Repository: HangfireIO/Hangfire Branch: main Commit: 333bd8eb2284 Files: 595 Total size: 4.3 MB Directory structure: gitextract_a8bklu6g/ ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .nuget/ │ ├── packages.config │ └── packages.lock.json ├── CONTRIBUTING.md ├── COPYING ├── COPYING.LESSER ├── Directory.Build.props ├── Hangfire.sln ├── Hangfire.sln.DotSettings ├── LICENSE.md ├── LICENSE_ROYALTYFREE ├── LICENSE_STANDARD ├── NOTICES ├── NuGet.config ├── README.md ├── SECURITY.md ├── appveyor.yml ├── build.bat ├── build.sh ├── coverity-scan.ps1 ├── nuspecs/ │ ├── Hangfire.AspNetCore.nuspec │ ├── Hangfire.Core.nuspec │ ├── Hangfire.NetCore.nuspec │ ├── Hangfire.SqlServer.MSMQ.nuspec │ ├── Hangfire.SqlServer.nuspec │ └── Hangfire.nuspec ├── psake-project.ps1 ├── samples/ │ ├── ConsoleSample/ │ │ ├── ConsoleSample.csproj │ │ ├── GenericServices.cs │ │ ├── NewFeatures.cs │ │ ├── Program.cs │ │ ├── Services.cs │ │ ├── Startup.cs │ │ ├── app.config │ │ └── packages.lock.json │ └── NetCoreSample/ │ ├── NetCoreSample.csproj │ ├── Program.cs │ └── packages.lock.json ├── src/ │ ├── Directory.Build.props │ ├── Hangfire.AspNetCore/ │ │ ├── Dashboard/ │ │ │ ├── AspNetCoreDashboardContext.cs │ │ │ ├── AspNetCoreDashboardContextExtensions.cs │ │ │ ├── AspNetCoreDashboardMiddleware.cs │ │ │ ├── AspNetCoreDashboardRequest.cs │ │ │ └── AspNetCoreDashboardResponse.cs │ │ ├── Hangfire.AspNetCore.csproj │ │ ├── HangfireApplicationBuilderExtensions.cs │ │ ├── HangfireEndpointRouteBuilderExtensions.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ └── packages.lock.json │ ├── Hangfire.Core/ │ │ ├── AppBuilderExtensions.cs │ │ ├── App_Packages/ │ │ │ ├── LibLog.1.4/ │ │ │ │ └── LibLog.cs │ │ │ ├── StackTraceFormatter/ │ │ │ │ └── StackTraceFormatter.cs │ │ │ └── StackTraceParser/ │ │ │ └── StackTraceParser.cs │ │ ├── AttemptsExceededAction.cs │ │ ├── AutomaticRetryAttribute.cs │ │ ├── BackgroundJob.Instance.cs │ │ ├── BackgroundJob.cs │ │ ├── BackgroundJobClient.cs │ │ ├── BackgroundJobClientException.cs │ │ ├── BackgroundJobClientExtensions.cs │ │ ├── BackgroundJobServer.cs │ │ ├── BackgroundJobServerOptions.cs │ │ ├── CaptureCultureAttribute.cs │ │ ├── Client/ │ │ │ ├── BackgroundJobFactory.cs │ │ │ ├── ClientExceptionContext.cs │ │ │ ├── CoreBackgroundJobFactory.cs │ │ │ ├── CreateContext.cs │ │ │ ├── CreatedContext.cs │ │ │ ├── CreatingContext.cs │ │ │ ├── IBackgroundJobFactory.cs │ │ │ ├── IClientExceptionFilter.cs │ │ │ └── IClientFilter.cs │ │ ├── Common/ │ │ │ ├── CachedExpressionCompiler.cs │ │ │ ├── CancellationTokenExtentions.cs │ │ │ ├── ExpressionUtil/ │ │ │ │ ├── BinaryExpressionFingerprint.cs │ │ │ │ ├── CachedExpressionCompiler.cs │ │ │ │ ├── ConditionalExpressionFingerprint.cs │ │ │ │ ├── ConstantExpressionFingerprint.cs │ │ │ │ ├── DefaultExpressionFingerprint.cs │ │ │ │ ├── ExpressionFingerprint.cs │ │ │ │ ├── ExpressionFingerprintChain.cs │ │ │ │ ├── FingerprintingExpressionVisitor.cs │ │ │ │ ├── HashCodeCombiner.cs │ │ │ │ ├── Hoisted.cs │ │ │ │ ├── HoistingExpressionVisitor.cs │ │ │ │ ├── IndexExpressionFingerprint.cs │ │ │ │ ├── LambdaExpressionFingerprint.cs │ │ │ │ ├── MemberExpressionFingerprint.cs │ │ │ │ ├── MethodCallExpressionFingerprint.cs │ │ │ │ ├── ParameterExpressionFingerprint.cs │ │ │ │ ├── TypeBinaryExpressionFingerprint.cs │ │ │ │ └── UnaryExpressionFingerprint.cs │ │ │ ├── IJobFilter.cs │ │ │ ├── IJobFilterProvider.cs │ │ │ ├── Job.cs │ │ │ ├── JobFilter.cs │ │ │ ├── JobFilterAttribute.cs │ │ │ ├── JobFilterAttributeFilterProvider.cs │ │ │ ├── JobFilterCollection.cs │ │ │ ├── JobFilterInfo.cs │ │ │ ├── JobFilterProviderCollection.cs │ │ │ ├── JobFilterProviders.cs │ │ │ ├── JobFilterScope.cs │ │ │ ├── JobHelper.cs │ │ │ ├── JobLoadException.cs │ │ │ ├── LanguagePolyfills.cs │ │ │ ├── MethodInfoExtensions.cs │ │ │ ├── ReflectedAttributeCache.cs │ │ │ ├── SerializationHelper.cs │ │ │ ├── ShallowExceptionHelper.cs │ │ │ ├── TypeExtensions.cs │ │ │ ├── TypeHelper.cs │ │ │ └── TypeHelperSerializationBinder.cs │ │ ├── ContinuationsSupportAttribute.cs │ │ ├── Cron.cs │ │ ├── Dashboard/ │ │ │ ├── BatchCommandDispatcher.cs │ │ │ ├── CombinedResourceDispatcher.cs │ │ │ ├── CommandDispatcher.cs │ │ │ ├── Content/ │ │ │ │ ├── css/ │ │ │ │ │ ├── hangfire-dark.css │ │ │ │ │ └── hangfire.css │ │ │ │ ├── js/ │ │ │ │ │ └── hangfire.js │ │ │ │ └── resx/ │ │ │ │ ├── Strings.Designer.cs │ │ │ │ ├── Strings.ca.resx │ │ │ │ ├── Strings.de.resx │ │ │ │ ├── Strings.es.resx │ │ │ │ ├── Strings.fa.resx │ │ │ │ ├── Strings.fr.resx │ │ │ │ ├── Strings.nb.resx │ │ │ │ ├── Strings.nl.resx │ │ │ │ ├── Strings.pt-BR.resx │ │ │ │ ├── Strings.pt-PT.resx │ │ │ │ ├── Strings.pt.resx │ │ │ │ ├── Strings.resx │ │ │ │ ├── Strings.sv.resx │ │ │ │ ├── Strings.tr-TR.resx │ │ │ │ ├── Strings.zh-TW.resx │ │ │ │ └── Strings.zh.resx │ │ │ ├── DashboardContext.cs │ │ │ ├── DashboardMetric.cs │ │ │ ├── DashboardMetrics.cs │ │ │ ├── DashboardRequest.cs │ │ │ ├── DashboardResponse.cs │ │ │ ├── DashboardRoutes.cs │ │ │ ├── EmbeddedResourceDispatcher.cs │ │ │ ├── HtmlHelper.cs │ │ │ ├── IDashboardAsyncAuthorizationFilter.cs │ │ │ ├── IDashboardAuthorizationFilter.cs │ │ │ ├── IDashboardDispatcher.cs │ │ │ ├── JobDetailsRenderer.cs │ │ │ ├── JobHistoryRenderer.cs │ │ │ ├── JobMethodCallRenderer.cs │ │ │ ├── JobsSidebarMenu.cs │ │ │ ├── JsonStats.cs │ │ │ ├── LocalRequestsOnlyAuthorizationFilter.cs │ │ │ ├── MenuItem.cs │ │ │ ├── Metric.cs │ │ │ ├── NavigationMenu.cs │ │ │ ├── NonEscapedString.cs │ │ │ ├── Owin/ │ │ │ │ ├── IOwinDashboardAntiforgery.cs │ │ │ │ ├── MiddlewareExtensions.cs │ │ │ │ ├── OwinDashboardContext.cs │ │ │ │ ├── OwinDashboardContextExtensions.cs │ │ │ │ ├── OwinDashboardRequest.cs │ │ │ │ └── OwinDashboardResponse.cs │ │ │ ├── Pager.cs │ │ │ ├── Pages/ │ │ │ │ ├── AwaitingJobsPage.cshtml │ │ │ │ ├── AwaitingJobsPage.cshtml.cs │ │ │ │ ├── DeletedJobsPage.cshtml │ │ │ │ ├── DeletedJobsPage.cshtml.cs │ │ │ │ ├── EnqueuedJobsPage.cs │ │ │ │ ├── EnqueuedJobsPage.cshtml │ │ │ │ ├── EnqueuedJobsPage.cshtml.cs │ │ │ │ ├── FailedJobsPage.cshtml │ │ │ │ ├── FailedJobsPage.cshtml.cs │ │ │ │ ├── FetchedJobsPage.cs │ │ │ │ ├── FetchedJobsPage.cshtml │ │ │ │ ├── FetchedJobsPage.cshtml.cs │ │ │ │ ├── HomePage.cs │ │ │ │ ├── HomePage.cshtml │ │ │ │ ├── HomePage.cshtml.cs │ │ │ │ ├── JobDetailsPage.cs │ │ │ │ ├── JobDetailsPage.cshtml │ │ │ │ ├── JobDetailsPage.cshtml.cs │ │ │ │ ├── LayoutPage.cs │ │ │ │ ├── LayoutPage.cshtml │ │ │ │ ├── LayoutPage.cshtml.cs │ │ │ │ ├── ProcessingJobsPage.cshtml │ │ │ │ ├── ProcessingJobsPage.cshtml.cs │ │ │ │ ├── QueuesPage.cshtml │ │ │ │ ├── QueuesPage.cshtml.cs │ │ │ │ ├── RecurringJobsPage.cshtml │ │ │ │ ├── RecurringJobsPage.cshtml.cs │ │ │ │ ├── RetriesPage.cshtml │ │ │ │ ├── RetriesPage.cshtml.cs │ │ │ │ ├── ScheduledJobsPage.cshtml │ │ │ │ ├── ScheduledJobsPage.cshtml.cs │ │ │ │ ├── ServersPage.cshtml │ │ │ │ ├── ServersPage.cshtml.cs │ │ │ │ ├── SucceededJobs.cshtml │ │ │ │ ├── SucceededJobs.cshtml.cs │ │ │ │ ├── _BlockMetric.cs │ │ │ │ ├── _BlockMetric.cshtml │ │ │ │ ├── _BlockMetric.cshtml.cs │ │ │ │ ├── _Breadcrumbs.cs │ │ │ │ ├── _Breadcrumbs.cshtml │ │ │ │ ├── _Breadcrumbs.cshtml.cs │ │ │ │ ├── _ErrorAlert.cshtml │ │ │ │ ├── _ErrorAlert.cshtml.cs │ │ │ │ ├── _InlineMetric.cs │ │ │ │ ├── _InlineMetric.cshtml │ │ │ │ ├── _InlineMetric.cshtml.cs │ │ │ │ ├── _Navigation.cshtml │ │ │ │ ├── _Navigation.cshtml.cs │ │ │ │ ├── _Paginator.cs │ │ │ │ ├── _Paginator.cshtml │ │ │ │ ├── _Paginator.cshtml.cs │ │ │ │ ├── _PerPageSelector.cs │ │ │ │ ├── _PerPageSelector.cshtml │ │ │ │ ├── _PerPageSelector.cshtml.cs │ │ │ │ ├── _SidebarMenu.cs │ │ │ │ ├── _SidebarMenu.cshtml │ │ │ │ └── _SidebarMenu.cshtml.cs │ │ │ ├── RazorPage.cs │ │ │ ├── RazorPageDispatcher.cs │ │ │ ├── RouteCollection.cs │ │ │ ├── RouteCollectionExtensions.cs │ │ │ └── UrlHelper.cs │ │ ├── DashboardOptions.cs │ │ ├── DisableConcurrentExecutionAttribute.cs │ │ ├── ExceptionInfo.cs │ │ ├── ExceptionTypeHelper.cs │ │ ├── FromParameterAttribute.cs │ │ ├── FromResultAttribute.cs │ │ ├── GlobalConfiguration.cs │ │ ├── GlobalConfigurationExtensions.cs │ │ ├── GlobalJobFilters.cs │ │ ├── GlobalStateHandlers.cs │ │ ├── Hangfire.Core.csproj │ │ ├── IBackgroundJobClient.cs │ │ ├── IGlobalConfiguration.cs │ │ ├── IJobCancellationToken.cs │ │ ├── IRecurringJobManager.cs │ │ ├── ITimeZoneResolver.cs │ │ ├── IdempotentCompletionAttribute.cs │ │ ├── JobActivator.cs │ │ ├── JobActivatorContext.cs │ │ ├── JobActivatorScope.cs │ │ ├── JobCancellationToken.cs │ │ ├── JobCancellationTokenExtensions.cs │ │ ├── JobContinuationOptions.cs │ │ ├── JobDisplayNameAttribute.cs │ │ ├── JobParameterInjectionFilter.cs │ │ ├── JobStorage.cs │ │ ├── LatencyTimeoutAttribute.cs │ │ ├── MisfireHandlingMode.cs │ │ ├── MoreLinq/ │ │ │ └── MoreEnumerable.Pairwise.cs │ │ ├── Obsolete/ │ │ │ ├── BootstrapperConfigurationExtensions.cs │ │ │ ├── CreateJobFailedException.cs │ │ │ ├── DashboardMiddleware.cs │ │ │ ├── DashboardOwinExtensions.cs │ │ │ ├── IAuthorizationFilter.cs │ │ │ ├── IBootstrapperConfiguration.cs │ │ │ ├── IRequestDispatcher.cs │ │ │ ├── IServerComponent.cs │ │ │ ├── IServerProcess.cs │ │ │ ├── Job.Obsolete.cs │ │ │ ├── OwinBootstrapper.cs │ │ │ ├── RequestDispatcherContext.cs │ │ │ ├── RequestDispatcherWrapper.cs │ │ │ ├── ServerOwinExtensions.cs │ │ │ ├── ServerWatchdogOptions.cs │ │ │ ├── StartupConfiguration.cs │ │ │ └── StateContext.cs │ │ ├── Processing/ │ │ │ ├── AppDomainUnloadMonitor.cs │ │ │ ├── BackgroundDispatcher.cs │ │ │ ├── BackgroundDispatcherAsync.cs │ │ │ ├── BackgroundExecution.cs │ │ │ ├── BackgroundExecutionOptions.cs │ │ │ ├── BackgroundTaskScheduler.cs │ │ │ ├── IBackgroundDispatcher.cs │ │ │ ├── IBackgroundExecution.cs │ │ │ ├── InlineSynchronizationContext.cs │ │ │ └── TaskExtensions.cs │ │ ├── Profiling/ │ │ │ ├── EmptyProfiler.cs │ │ │ ├── IProfiler.cs │ │ │ ├── ProfilerExtensions.cs │ │ │ └── SlowLogProfiler.cs │ │ ├── Properties/ │ │ │ ├── Annotations.cs │ │ │ ├── AssemblyInfo.cs │ │ │ └── NamespaceDoc.cs │ │ ├── QueueAttribute.cs │ │ ├── Razor.build │ │ ├── RecurringJob.cs │ │ ├── RecurringJobEntity.cs │ │ ├── RecurringJobExtensions.cs │ │ ├── RecurringJobManager.cs │ │ ├── RecurringJobManagerExtensions.cs │ │ ├── RecurringJobOptions.cs │ │ ├── Server/ │ │ │ ├── AspNetShutdownDetector.cs │ │ │ ├── BackgroundJobPerformer.cs │ │ │ ├── BackgroundProcessContext.cs │ │ │ ├── BackgroundProcessDispatcherBuilder.cs │ │ │ ├── BackgroundProcessDispatcherBuilderAsync.cs │ │ │ ├── BackgroundProcessExtensions.cs │ │ │ ├── BackgroundProcessingServer.cs │ │ │ ├── BackgroundProcessingServerOptions.cs │ │ │ ├── BackgroundServerContext.cs │ │ │ ├── BackgroundServerProcess.cs │ │ │ ├── CoreBackgroundJobPerformer.cs │ │ │ ├── DelayedJobScheduler.cs │ │ │ ├── IBackgroundJobPerformer.cs │ │ │ ├── IBackgroundProcess.cs │ │ │ ├── IBackgroundProcessAsync.cs │ │ │ ├── IBackgroundProcessDispatcherBuilder.cs │ │ │ ├── IBackgroundProcessingServer.cs │ │ │ ├── IBackgroundServerProcess.cs │ │ │ ├── IServerExceptionFilter.cs │ │ │ ├── IServerFilter.cs │ │ │ ├── JobAbortedException.cs │ │ │ ├── JobPerformanceException.cs │ │ │ ├── PerformContext.cs │ │ │ ├── PerformedContext.cs │ │ │ ├── PerformingContext.cs │ │ │ ├── RecurringJobScheduler.cs │ │ │ ├── ServerContext.cs │ │ │ ├── ServerExceptionContext.cs │ │ │ ├── ServerHeartbeatProcess.cs │ │ │ ├── ServerJobCancellationToken.cs │ │ │ ├── ServerJobCancellationWatcher.cs │ │ │ ├── ServerProcessDispatcherBuilder.cs │ │ │ ├── ServerWatchdog.cs │ │ │ └── Worker.cs │ │ ├── States/ │ │ │ ├── ApplyStateContext.cs │ │ │ ├── AwaitingState.cs │ │ │ ├── BackgroundJobStateChanger.cs │ │ │ ├── CoreStateMachine.cs │ │ │ ├── DeletedState.cs │ │ │ ├── ElectStateContext.cs │ │ │ ├── EnqueuedState.cs │ │ │ ├── FailedState.cs │ │ │ ├── IApplyStateFilter.cs │ │ │ ├── IBackgroundJobStateChanger.cs │ │ │ ├── IElectStateFilter.cs │ │ │ ├── IState.cs │ │ │ ├── IStateHandler.cs │ │ │ ├── IStateMachine.cs │ │ │ ├── ProcessingState.cs │ │ │ ├── ScheduledState.cs │ │ │ ├── StateChangeContext.cs │ │ │ ├── StateHandlerCollection.cs │ │ │ ├── StateMachine.cs │ │ │ └── SucceededState.cs │ │ ├── StatisticsHistoryAttribute.cs │ │ ├── Storage/ │ │ │ ├── BackgroundServerGoneException.cs │ │ │ ├── DistributedLockTimeoutException.cs │ │ │ ├── IFetchedJob.cs │ │ │ ├── IMonitoringApi.cs │ │ │ ├── IStorageConnection.cs │ │ │ ├── IWriteOnlyTransaction.cs │ │ │ ├── InvocationData.cs │ │ │ ├── JobData.cs │ │ │ ├── JobStorageConnection.cs │ │ │ ├── JobStorageFeatures.cs │ │ │ ├── JobStorageMonitor.cs │ │ │ ├── JobStorageTransaction.cs │ │ │ ├── Monitoring/ │ │ │ │ ├── AwaitingJobDto.cs │ │ │ │ ├── DeletedJobDto.cs │ │ │ │ ├── EnqueuedJobDto.cs │ │ │ │ ├── FailedJobDto.cs │ │ │ │ ├── FetchedJobDto.cs │ │ │ │ ├── JobDetailsDto.cs │ │ │ │ ├── JobList.cs │ │ │ │ ├── ProcessingJobDto.cs │ │ │ │ ├── QueueWithTopEnqueuedJobsDto.cs │ │ │ │ ├── ScheduledJobDto.cs │ │ │ │ ├── ServerDto.cs │ │ │ │ ├── StateHistoryDto.cs │ │ │ │ ├── StatisticsDto.cs │ │ │ │ └── SucceededJobDto.cs │ │ │ ├── RecurringJobDto.cs │ │ │ ├── StateData.cs │ │ │ └── StorageConnectionExtensions.cs │ │ └── packages.lock.json │ ├── Hangfire.NetCore/ │ │ ├── AspNetCore/ │ │ │ ├── AspNetCoreJobActivator.cs │ │ │ ├── AspNetCoreJobActivatorScope.cs │ │ │ ├── AspNetCoreLog.cs │ │ │ └── AspNetCoreLogProvider.cs │ │ ├── BackgroundJobServerHostedService.cs │ │ ├── BackgroundProcessingServerHostedService.cs │ │ ├── DefaultClientManagerFactory.cs │ │ ├── Hangfire.NetCore.csproj │ │ ├── HangfireServiceCollectionExtensions.cs │ │ ├── IBackgroundJobClientFactory.cs │ │ ├── IRecurringJobManagerFactory.cs │ │ └── packages.lock.json │ ├── Hangfire.SqlServer/ │ │ ├── Constants.cs │ │ ├── CountersAggregator.cs │ │ ├── DbCommandExtensions.cs │ │ ├── DefaultInstall.sql │ │ ├── DefaultInstall.tt │ │ ├── EnqueuedAndFetchedCountDto.cs │ │ ├── Entities/ │ │ │ ├── JobParameter.cs │ │ │ ├── Server.cs │ │ │ ├── ServerData.cs │ │ │ ├── SqlHash.cs │ │ │ ├── SqlJob.cs │ │ │ └── SqlState.cs │ │ ├── ExceptionTypeHelper.cs │ │ ├── ExpirationManager.cs │ │ ├── Hangfire.SqlServer.csproj │ │ ├── IPersistentJobQueue.cs │ │ ├── IPersistentJobQueueMonitoringApi.cs │ │ ├── IPersistentJobQueueProvider.cs │ │ ├── Install.sql │ │ ├── PersistentJobQueueProviderCollection.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── SqlCommandBatch.cs │ │ ├── SqlCommandSet.cs │ │ ├── SqlServerBootstrapperConfigurationExtensions.cs │ │ ├── SqlServerConnection.cs │ │ ├── SqlServerDistributedLock.cs │ │ ├── SqlServerDistributedLockException.cs │ │ ├── SqlServerHeartbeatProcess.cs │ │ ├── SqlServerJobQueue.cs │ │ ├── SqlServerJobQueueMonitoringApi.cs │ │ ├── SqlServerJobQueueProvider.cs │ │ ├── SqlServerMonitoringApi.cs │ │ ├── SqlServerObjectsInstaller.cs │ │ ├── SqlServerStorage.cs │ │ ├── SqlServerStorageExtensions.cs │ │ ├── SqlServerStorageOptions.cs │ │ ├── SqlServerTimeoutJob.cs │ │ ├── SqlServerTransactionJob.cs │ │ ├── SqlServerWriteOnlyTransaction.cs │ │ ├── TimestampHelper.cs │ │ └── packages.lock.json │ ├── Hangfire.SqlServer.Msmq/ │ │ ├── Hangfire.SqlServer.Msmq.csproj │ │ ├── IMsmqTransaction.cs │ │ ├── MessageQueueExtensions.cs │ │ ├── MsmqDtcTransaction.cs │ │ ├── MsmqExtensions.cs │ │ ├── MsmqFetchedJob.cs │ │ ├── MsmqInternalTransaction.cs │ │ ├── MsmqJobQueue.cs │ │ ├── MsmqJobQueueMonitoringApi.cs │ │ ├── MsmqJobQueueProvider.cs │ │ ├── MsmqSqlServerStorageExtensions.cs │ │ ├── MsmqTransactionType.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ └── packages.lock.json │ └── SharedAssemblyInfo.cs └── tests/ ├── Directory.Build.props ├── Hangfire.Core.Tests/ │ ├── BackgroundJobClientExtensionsFacts.cs │ ├── BackgroundJobClientFacts.cs │ ├── BackgroundJobFacts.cs │ ├── BackgroundJobServerFacts.cs │ ├── CaptureCultureAttributeFacts.cs │ ├── Client/ │ │ ├── BackgroundJobFactoryFacts.cs │ │ ├── ClientExceptionContextFacts.cs │ │ ├── CoreBackgroundJobFactoryFacts.cs │ │ ├── CreateContextFacts.cs │ │ ├── CreatedContextFacts.cs │ │ └── CreatingContextFacts.cs │ ├── Common/ │ │ ├── CancellationTokenExtentionsFacts.cs │ │ ├── JobArgumentFacts.cs │ │ ├── JobFacts.cs │ │ ├── JobFilterAttributeFacts.cs │ │ ├── JobFilterAttributeFilterProviderFacts.cs │ │ ├── JobFilterCollectionFacts.cs │ │ ├── JobFilterFacts.cs │ │ ├── JobFilterProviderCollectionFacts.cs │ │ ├── JobHelperFacts.cs │ │ ├── JobLoadExceptionFacts.cs │ │ ├── MethodInfoExtensionsFacts.cs │ │ ├── SerializationHelperFacts.cs │ │ ├── ShallowExceptionHelperFacts.cs │ │ └── TypeExtensionsFacts.cs │ ├── ContinuationsSupportAttributeFacts.cs │ ├── CronFacts.cs │ ├── Dashboard/ │ │ ├── BatchCommandDispatcherFacts.cs │ │ ├── CommandDispatcherFacts.cs │ │ ├── DashboardOptionsFacts.cs │ │ └── HtmlHelperFacts.cs │ ├── GlobalConfigurationExtensionsFacts.cs │ ├── GlobalStateHandlersFacts.cs │ ├── GlobalTestsConfiguration.cs │ ├── Hangfire.Core.Tests.csproj │ ├── Hangfire.Core.Tests.csproj.DotSettings │ ├── JobActivatorFacts.cs │ ├── JobCancellationTokenFacts.cs │ ├── JobParameterInjectionFilterFacts.cs │ ├── JobStorageFacts.cs │ ├── LatencyTimeoutAttributeFacts.cs │ ├── Mocks/ │ │ ├── ApplyStateContextMock.cs │ │ ├── BackgroundJobMock.cs │ │ ├── BackgroundProcessContextMock.cs │ │ ├── CreateContextMock.cs │ │ ├── ElectStateContextMock.cs │ │ ├── PerformContextMock.cs │ │ └── StateChangeContextMock.cs │ ├── Obsolete/ │ │ └── ServerWatchdogOptionsFacts.cs │ ├── PreserveCultureAttributeFacts.cs │ ├── Processing/ │ │ └── TaskExtensionsFacts.cs │ ├── Profiling/ │ │ └── ProfilerFacts.cs │ ├── QueueAttributeFacts.cs │ ├── RecurringJobEntityFacts.cs │ ├── RecurringJobManagerFacts.cs │ ├── RecurringJobOptionsFacts.cs │ ├── RetryAttributeFacts.cs │ ├── Server/ │ │ ├── BackgroundJobPerformerFacts.cs │ │ ├── BackgroundJobServerOptionsFacts.cs │ │ ├── BackgroundProcessContextFacts.cs │ │ ├── BackgroundProcessingServerFacts.cs │ │ ├── CoreBackgroundJobPerformerFacts.cs │ │ ├── DelayedJobSchedulerFacts.cs │ │ ├── PerformContextFacts.cs │ │ ├── RecurringJobSchedulerFacts.cs │ │ ├── ServerJobCancellationTokenFacts.cs │ │ ├── ServerJobCancellationWatcherFacts.cs │ │ ├── ServerWatchdogFacts.cs │ │ └── WorkerFacts.cs │ ├── States/ │ │ ├── ApplyStateContextFacts.cs │ │ ├── AwaitingStateFacts.cs │ │ ├── AwaitingStateHandlerFacts.cs │ │ ├── BackgroundJobStateChangerFacts.cs │ │ ├── CoreStateMachineFacts.cs │ │ ├── DeletedStateFacts.cs │ │ ├── DeletedStateHandlerFacts.cs │ │ ├── ElectStateContextFacts.cs │ │ ├── EnqueuedStateFacts.cs │ │ ├── EnqueuedStateHandlerFacts.cs │ │ ├── EnqueuedStateValidationFacts.cs │ │ ├── FailedStateFacts.cs │ │ ├── ProcessingStateFacts.cs │ │ ├── ScheduledStateFacts.cs │ │ ├── ScheduledStateHandlerFacts.cs │ │ ├── StateChangeContextFacts.cs │ │ ├── StateHandlerCollectionFacts.cs │ │ ├── StateMachineFacts.cs │ │ ├── SucceededStateFacts.cs │ │ └── SucceededStateHandlerFacts.cs │ ├── StatisticsHistoryAttributeFacts.cs │ ├── Storage/ │ │ ├── InvocationDataFacts.cs │ │ ├── MonitoringTypeFacts.cs │ │ └── StorageConnectionExtensionsFacts.cs │ ├── Stubs/ │ │ ├── DashboardContextStub.cs │ │ ├── DashboardResponseStub.cs │ │ └── JobStorageStub.cs │ ├── Utils/ │ │ ├── CleanSerializerSettingsAttribute.cs │ │ ├── CultureHelper.cs │ │ ├── DataCompatibilityRangeFactAttribute.cs │ │ ├── DataCompatibilityRangeFactDiscoverer.cs │ │ ├── DataCompatibilityRangeTestCase.cs │ │ ├── DataCompatibilityRangeTestCaseRunner.cs │ │ ├── DataCompatibilityRangeTestRunner.cs │ │ ├── DataCompatibilityRangeTheoryAttribute.cs │ │ ├── DataCompatibilityRangeTheoryDiscoverer.cs │ │ ├── DataCompatibilityRangeTheoryTestCase.cs │ │ ├── DataCompatibilityRangeTheoryTestCaseRunner.cs │ │ ├── GlobalLockAttribute.cs │ │ ├── PlatformHelper.cs │ │ ├── SequenceAttribute.cs │ │ ├── SerializerSettingsHelper.cs │ │ └── StaticLockAttribute.cs │ └── packages.lock.json ├── Hangfire.SqlServer.Msmq.Tests/ │ ├── Hangfire.SqlServer.Msmq.Tests.csproj │ ├── MessageQueueExtensionsFacts.cs │ ├── MsmqJobQueueFacts.cs │ ├── MsmqJobQueueMonitoringApiFacts.cs │ ├── MsmqJobQueueProviderFacts.cs │ ├── MsmqSqlServerStorageExtensionsFacts.cs │ ├── Utils/ │ │ ├── CleanMsmqQueueAttribute.cs │ │ └── MsmqUtils.cs │ └── packages.lock.json └── Hangfire.SqlServer.Tests/ ├── CountersAggregatorFacts.cs ├── ExpirationManagerFacts.cs ├── GlobalTestsConfiguration.cs ├── Hangfire.SqlServer.Tests.csproj ├── PersistentJobQueueProviderCollectionFacts.cs ├── SqlServerConnectionFacts.cs ├── SqlServerDistributedLockFacts.cs ├── SqlServerJobQueueFacts.cs ├── SqlServerMonitoringApiFacts.cs ├── SqlServerStorageFacts.cs ├── SqlServerTimeoutJobFacts.cs ├── SqlServerTransactionJobFacts.cs ├── SqlServerWriteOnlyTransactionFacts.cs ├── StorageOptionsFacts.cs ├── Utils/ │ ├── CleanDatabaseAttribute.cs │ ├── CleanSerializerSettingsAttribute.cs │ ├── ConnectionUtils.cs │ └── SerializerSettingsHelper.cs └── packages.lock.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*.cs,*.ps1] indent_style = space indent_size = 4 ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp text *.sln merge=union text eol=crlf *.csproj merge=union text eol=crlf *.vbproj merge=union text eol=crlf *.fsproj merge=union text eol=crlf *.dbproj merge=union text eol=crlf # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain # Source code *.cshtml text *.css text *.xml text *.bat text *.ps1 text *.nuspec text *.js text *.json text *.sql text # Documentation *.md text *.txt text *.manifest text COPYING text COPYING.LESSER text # Configs *.config text .editorconfig text .gitattributes text .gitignore text *.yml text *.DotSettings text ================================================ FILE: .gitignore ================================================ ################# ## IDEA Rider ################# .idea .idea.* ################# ## Eclipse ################# *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates packages/ # Build results [Bb]uild/ [Dd]ebug/ [Rr]elease/ x64/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # dotnet artifacts/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* coverage.xml *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.log *.scc # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.Publish.xml *.pubxml # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.[Pp]ublish.xml *.pfx *.publishsettings # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files App_Data/*.mdf App_Data/*.ldf ############# ## Windows detritus ############# # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Mac crap .DS_Store ############# ## Python ############# *.py[co] # Packages *.egg *.egg-info dist/ eggs/ parts/ var/ sdist/ develop-eggs/ .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg #Sphinx Built Docs docs/_build #Jekyll site builds _site *.userprefs cov-int/ ================================================ FILE: .nuget/packages.config ================================================  ================================================ FILE: .nuget/packages.lock.json ================================================ { "version": 1, "dependencies": { "Any,Version=v0.0": { "Hangfire.Build": { "type": "Direct", "requested": "[0.5.0, 0.5.0]", "resolved": "0.5.0", "contentHash": "4yRCdMaDr6cyFRmCvpFO8kBMV57KPOofugaHOsjkDEDw+G/BCGWOdrpXfkAeTEtZBPUv2jS0PYmVNK5680KxXQ==" }, "ILRepack": { "type": "Direct", "requested": "[2.0.27, 2.0.27]", "resolved": "2.0.27", "contentHash": "N5nhVwlgU2ipeonLBVuv1HpsIclJI/5QujuLeo3BW7irD2KnSFXkHa5FmXwzdCkB36D0XGOxNMxh99P7kjgU2A==" }, "psake": { "type": "Direct", "requested": "[4.4.1, 4.4.1]", "resolved": "4.4.1", "contentHash": "Hn5kdGPEoapi+wAAjaGjKEZVnuYp7fUrPK3IivLYG6Bn4adhd8l+KXXPMEmte41RmrLvfV7XGZa9KsSTc0gjDA==" }, "RazorGenerator.MsBuild": { "type": "Direct", "requested": "[2.5.0, 2.5.0]", "resolved": "2.5.0", "contentHash": "yOu2KEjDFvE20b+YK/+Ovf4czUHWH2DlBB9lIKDXdBjcMh3PXrPJTxPzi468XrFvE1dVEiWu+pGNkVFE9+VpMA==" } } } } ================================================ FILE: CONTRIBUTING.md ================================================ # File an Issue If you have a question rather than an issue, please post it to the [Hangfire Stack Overflow tag](https://stackoverflow.com/questions/tagged/hangfire). For non-security related bugs please log a new issue: 1. Search the [issue tracker](https://github.com/HangfireIO/Hangfire/issues) for similar issues. 2. Specify the **version** of `Hangfire.Core` package in which the bug was occurred. 3. Specify the **storage** package (e.g. `Hangfire.SqlServer`) you are using and its exact version. 4. Specify the **configuration** logic for Hangfire. 5. Specify all the custom job **filters** if any, and post their source code. 6. Describe the problem and your environment in detail (i.e. what happened and what you expected would happen). ProTip! * Include screenshots from Dashboard UI, to allow us to see the same problem. You can simply use Print Screen, then Ctrl + V directly into the comment window on GitHub. * Include log messages, written by Hangfire when a problem occurred. Don't forget to tell your logger to dump all the exception details. * Include stack trace dump, if your background processing is stuck. You can use [`stdump`](https://github.com/odinserj/stdump) utility to get them either from a minidump file, or from a running process without interrupting it: `stdump w3wp > stacktrace.txt` Hints * Use [syntax highlighting](https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting) for your C#, SQL, etc. code blocks. * Use [fenced code blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/#fenced-code-blocks) for exception details. # Reporting security issues In order to give the community time to respond and upgrade we strongly urge you report all security issues privately. Please email us at [security@hangfire.io](mailto:security@hangfire.io) with details and we will respond ASAP. Security issues always take precedence over bug fixes and feature work. We can and do mark releases as "urgent" if they contain serious security fixes. ================================================ FILE: COPYING ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: COPYING.LESSER ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: Directory.Build.props ================================================ true true true ================================================ FILE: Hangfire.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28307.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleSample", "samples\ConsoleSample\ConsoleSample.csproj", "{C02BB718-2AE4-434C-8668-C894FF663FCE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Core", "src\Hangfire.Core\Hangfire.Core.csproj", "{C995EA9E-56EE-4951-8260-D94260A7F4C2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{119DA7FA-B94C-4B63-AEC9-428EF834E0D8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.SqlServer", "src\Hangfire.SqlServer\Hangfire.SqlServer.csproj", "{A523C0E3-097D-4869-977F-15A717EA3E83}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{766BE831-F758-46BC-AFD3-BBEEFE0F686F}" ProjectSection(SolutionItems) = preProject tests\Directory.Build.props = tests\Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{15E186DF-8918-44F1-9285-9756EDAECA5D}" ProjectSection(SolutionItems) = preProject appveyor.yml = appveyor.yml psake-project.ps1 = psake-project.ps1 src\SharedAssemblyInfo.cs = src\SharedAssemblyInfo.cs src\Directory.Build.props = src\Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuspecs", "nuspecs", "{5E687025-A525-4534-8869-CB685C7B11C4}" ProjectSection(SolutionItems) = preProject nuspecs\Hangfire.AspNetCore.nuspec = nuspecs\Hangfire.AspNetCore.nuspec nuspecs\Hangfire.Core.nuspec = nuspecs\Hangfire.Core.nuspec nuspecs\Hangfire.NetCore.nuspec = nuspecs\Hangfire.NetCore.nuspec nuspecs\Hangfire.nuspec = nuspecs\Hangfire.nuspec nuspecs\Hangfire.SqlServer.MSMQ.nuspec = nuspecs\Hangfire.SqlServer.MSMQ.nuspec nuspecs\Hangfire.SqlServer.nuspec = nuspecs\Hangfire.SqlServer.nuspec EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{37E2BF00-C473-48A5-B2A7-5A3DE0910FCF}" ProjectSection(SolutionItems) = preProject content\readme.txt = content\readme.txt EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Core.Tests", "tests\Hangfire.Core.Tests\Hangfire.Core.Tests.csproj", "{E13C3543-39A3-475C-BB43-2E311E634843}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.SqlServer.Tests", "tests\Hangfire.SqlServer.Tests\Hangfire.SqlServer.Tests.csproj", "{6DFFA275-C483-4501-823A-741AC9EC0846}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.SqlServer.Msmq", "src\Hangfire.SqlServer.Msmq\Hangfire.SqlServer.Msmq.csproj", "{762BE479-0AEC-47E0-8F9C-34FA54641749}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.SqlServer.Msmq.Tests", "tests\Hangfire.SqlServer.Msmq.Tests\Hangfire.SqlServer.Msmq.Tests.csproj", "{DBC6BC12-06AD-4597-9E0F-B77BE754FA2C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F710C9CF-69F9-4373-9B74-ABB185BF985B}" ProjectSection(SolutionItems) = preProject .nuget\packages.config = .nuget\packages.config EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.AspNetCore", "src\Hangfire.AspNetCore\Hangfire.AspNetCore.csproj", "{73AFEBE0-9B11-44F6-A69C-8A51538CF18E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreSample", "samples\NetCoreSample\NetCoreSample.csproj", "{FA751692-20C8-4986-8164-D21F505B2172}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hangfire.NetCore", "src\Hangfire.NetCore\Hangfire.NetCore.csproj", "{AA8E3A67-8731-423A-9B69-EE44EC6B8E1A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C02BB718-2AE4-434C-8668-C894FF663FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C02BB718-2AE4-434C-8668-C894FF663FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU {C02BB718-2AE4-434C-8668-C894FF663FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU {C02BB718-2AE4-434C-8668-C894FF663FCE}.Release|Any CPU.Build.0 = Release|Any CPU {C995EA9E-56EE-4951-8260-D94260A7F4C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C995EA9E-56EE-4951-8260-D94260A7F4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C995EA9E-56EE-4951-8260-D94260A7F4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {C995EA9E-56EE-4951-8260-D94260A7F4C2}.Release|Any CPU.Build.0 = Release|Any CPU {A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A523C0E3-097D-4869-977F-15A717EA3E83}.Debug|Any CPU.Build.0 = Debug|Any CPU {A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.ActiveCfg = Release|Any CPU {A523C0E3-097D-4869-977F-15A717EA3E83}.Release|Any CPU.Build.0 = Release|Any CPU {E13C3543-39A3-475C-BB43-2E311E634843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E13C3543-39A3-475C-BB43-2E311E634843}.Debug|Any CPU.Build.0 = Debug|Any CPU {E13C3543-39A3-475C-BB43-2E311E634843}.Release|Any CPU.ActiveCfg = Release|Any CPU {E13C3543-39A3-475C-BB43-2E311E634843}.Release|Any CPU.Build.0 = Release|Any CPU {6DFFA275-C483-4501-823A-741AC9EC0846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6DFFA275-C483-4501-823A-741AC9EC0846}.Debug|Any CPU.Build.0 = Debug|Any CPU {6DFFA275-C483-4501-823A-741AC9EC0846}.Release|Any CPU.ActiveCfg = Release|Any CPU {6DFFA275-C483-4501-823A-741AC9EC0846}.Release|Any CPU.Build.0 = Release|Any CPU {762BE479-0AEC-47E0-8F9C-34FA54641749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {762BE479-0AEC-47E0-8F9C-34FA54641749}.Debug|Any CPU.Build.0 = Debug|Any CPU {762BE479-0AEC-47E0-8F9C-34FA54641749}.Release|Any CPU.ActiveCfg = Release|Any CPU {762BE479-0AEC-47E0-8F9C-34FA54641749}.Release|Any CPU.Build.0 = Release|Any CPU {DBC6BC12-06AD-4597-9E0F-B77BE754FA2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DBC6BC12-06AD-4597-9E0F-B77BE754FA2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {DBC6BC12-06AD-4597-9E0F-B77BE754FA2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBC6BC12-06AD-4597-9E0F-B77BE754FA2C}.Release|Any CPU.Build.0 = Release|Any CPU {73AFEBE0-9B11-44F6-A69C-8A51538CF18E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {73AFEBE0-9B11-44F6-A69C-8A51538CF18E}.Debug|Any CPU.Build.0 = Debug|Any CPU {73AFEBE0-9B11-44F6-A69C-8A51538CF18E}.Release|Any CPU.ActiveCfg = Release|Any CPU {73AFEBE0-9B11-44F6-A69C-8A51538CF18E}.Release|Any CPU.Build.0 = Release|Any CPU {FA751692-20C8-4986-8164-D21F505B2172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FA751692-20C8-4986-8164-D21F505B2172}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA751692-20C8-4986-8164-D21F505B2172}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA751692-20C8-4986-8164-D21F505B2172}.Release|Any CPU.Build.0 = Release|Any CPU {AA8E3A67-8731-423A-9B69-EE44EC6B8E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA8E3A67-8731-423A-9B69-EE44EC6B8E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA8E3A67-8731-423A-9B69-EE44EC6B8E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA8E3A67-8731-423A-9B69-EE44EC6B8E1A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {C02BB718-2AE4-434C-8668-C894FF663FCE} = {119DA7FA-B94C-4B63-AEC9-428EF834E0D8} {E13C3543-39A3-475C-BB43-2E311E634843} = {766BE831-F758-46BC-AFD3-BBEEFE0F686F} {6DFFA275-C483-4501-823A-741AC9EC0846} = {766BE831-F758-46BC-AFD3-BBEEFE0F686F} {DBC6BC12-06AD-4597-9E0F-B77BE754FA2C} = {766BE831-F758-46BC-AFD3-BBEEFE0F686F} {FA751692-20C8-4986-8164-D21F505B2172} = {119DA7FA-B94C-4B63-AEC9-428EF834E0D8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7597454C-C272-4016-87A2-CF1196347355} EndGlobalSection EndGlobal ================================================ FILE: Hangfire.sln.DotSettings ================================================  True True True True True True True True True True <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Hangfire.Core.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /><Filter ModuleMask="Hangfire.SqlServer.Tests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> <data /> True True True ================================================ FILE: LICENSE.md ================================================ License ======== Copyright © 2013-2026 Hangfire OÜ. Hangfire software is an open-source software that is multi-licensed under the terms of the licenses listed in this file. Recipients may choose the terms under which they are want to use or distribute the software, when all the preconditions of a chosen license are satisfied. LGPL v3 License --------------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. Please see COPYING.LESSER and COPYING files for details. Commercial License ------------------ Subject to the purchase of a corresponding subscription (please see https://www.hangfire.io/pricing/), you may distribute Hangfire under the terms of commercial license, that allows you to distribute private forks and modifications. Please see LICENSE_STANDARD and LICENSE_ROYALTYFREE files for details. ================================================ FILE: LICENSE_ROYALTYFREE ================================================ Royalty-free End-user License Agreement ======================================= THIS LICENSE AGREEMENT DESCRIBES YOUR RIGHTS WITH RESPECT TO THE SOFTWARE AND ITS COMPONENTS. 1. OWNERSHIP, LICENSE GRANT This is a license agreement and not an agreement for sale. We reserve ownership of all intellectual property rights inherent in or relating to the Software, which include, but are not limited to, all copyright, patent rights, all rights in relation to registered and unregistered trademarks (including service marks), confidential information (including trade secrets and know-how) and all rights other than those expressly granted by this License Agreement. Subject to the payment of the fee required and subject to the terms and conditions of this License Agreement, We grant to You a revocable, non- transferable and non-exclusive license (i) for Designated User(s) (as defined below) within Your organization to install and use the Software on any workstations used exclusively by such Designated User and (ii) for You to install and use the Software in connection with unlimited domains and sub-domains on unlimited servers, solely in connection with distribution of the Software in accordance with sections 3 and 4 below. This license is not sublicensable except as explicitly set forth herein. "Designated User(s)" shall mean Your employee(s) acting within the scope of their employment or Your consultant(s) or contractor(s) acting within the scope of the services they provide for You or on Your behalf for whom You have purchased a license to use the Software. 2. PERMITTED USES, SOURCE CODE, MODIFICATIONS We provide You with source code so that You can create Modifications of the original Software, where Modification means: a) any addition to or deletion from the contents of a file included in the original Software or previous Modifications created by You, or b) any new file that contains any part of the original Software or previous Modifications. While You retain all rights to any original work authored by You as part of the Modifications, We continue to own all copyright and other intellectual property rights in the Software. 3. DISTRIBUTION You may distribute the Software in any applications, frameworks, or elements (collectively referred to as an "Application" or "Applications") that you develop using the Software in accordance with this License Agreement, provided that such distribution does not violate the restrictions set forth in section 4 of this License Agreement. You must not remove, obscure or interfere with any copyright, acknowledgment, attribution, trademark, warning or disclaimer statement affixed to, incorporated in or otherwise applied in connection with the Software. You are required to ensure that the Software is not reused by or with any applications other than those with which You distribute it as permitted herein. For example, if You install the Software on a customer's server, that customer is not permitted to use the Software independently of Your application, and must be informed as such. You will not owe Us any royalties for Your distribution of the Software in accordance with this License Agreement. 4. PROHIBITED USES You may not, without Our prior written consent, redistribute the Software or Modifications other than by including the Software or a portion thereof within Your own product, which must have substantially different functionality than the Software or Modifications and must not allow any third party to use the Software or Modifications, or any portions thereof, for software development or application development purposes. You are explicitly not allowed to redistribute the Software or Modifications as part of any product that can be described as a development toolkit or library, an application builder, a website builder or any product that is intended for use by software, application, or website developers or designers. You may not change or remove the copyright notice from any of the files included in the Software or Modifications. You may redistribute the Software as part of Your own products. UNDER NO CIRCUMSTANCES MAY YOU USE THE SOFTWARE FOR A PRODUCT THAT IS INTENDED FOR SOFTWARE OR APPLICATION DEVELOPMENT PURPOSES. 5. TERMINATION This License Agreement and Your right to use the Software and Modifications will terminate immediately without notice if You fail to comply with the terms and conditions of this License Agreement. Upon termination, You agree to immediately cease using and destroy the Software or Modifications, including all accompanying documents. The provisions of sections 4, 5, 6, 7, 8, 9, 10 and 11 will survive any termination of this License Agreement. 6. DISCLAIMER OF WARRANTIES TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, WE AND OUR SUPPLIERS DISCLAIM ALL WARRANTIES AND CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT, WITH REGARD TO THE SOFTWARE. WE DO NOT GUARANTEE THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, AND YOU ACKNOWLEDGE THAT IT IS NOT TECHNICALLY PRACTICABLE FOR US TO DO SO. 7. LIMITATION OF LIABILITIES TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL WE OR OUR SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION OR ANY OTHER PECUNIARY LAW) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN ANY CASE, OUR ENTIRE LIABILITY UNDER ANY PROVISION OF THIS LICENSE AGREEMENT SHALL BE LIMITED TO THE AMOUNT ACTUALLY PAID BY YOU FOR THE SOFTWARE. 8. VERIFICATION We or a certified auditor acting on Our behalf, may, upon its reasonable request and at its expense, audit You with respect to the use of the Software. Such audit may be conducted by mail, electronic means or through an in-person visit to Your place of business. Any such in-person audit shall be conducted during regular business hours at Your facilities and shall not unreasonably interfere with Your business activities. We shall not remove, copy, or redistribute any electronic material during the course of an audit. If an audit reveals that You are using the Software in a way that is in material violation of the terms of the License Agreement, then You shall pay Our reasonable costs of conducting the audit. In the case of a material violation, You agree to pay Us any amounts owing that are attributable to the unauthorized use. In the alternative, We reserve the right, at Our sole option, to terminate the licenses for the Software. 9. THIRD PARTY SOFTWARE The Software may contain third party open source software which requires notices and/or additional terms and conditions. Such required third party software notices and/or additional terms and conditions are located in the NOTICES file accompanying the Software distribution (also available at https://www.hangfire.io/licensing/third-party.html), and are made a part of and incorporated by reference into this Agreement. By accepting this Agreement, you are also accepting the additional terms and conditions, if any, set forth therein. 10. PAYMENT AND TAXES If credit has been extended to You by Us, all payments under this License Agreement are due within thirty (30) days of the date We mail an invoice to You. If We have not extended credit to You, You shall be required to make payment concurrent with the delivery of the Software by Us. All amounts payable are gross amounts but exclusive of any value added tax, use tax, sales tax or similar tax. You shall be entitled to withhold from payments any applicable withholding taxes and comply with all applicable tax and employment legislation. Each party shall pay all taxes (including, but not limited to, taxes based upon its income) or levies imposed on it under applicable laws, regulations and tax treaties as a result of this Agreement and any payments made hereunder (including those required to be withheld or deducted from payments). Each party shall furnish evidence of such paid taxes as is sufficient to enable the other party to obtain any credits available to it, including original withholding tax certificates. 11. MISCELLANEOUS The license granted herein applies only to the version of the Software available when purchased in connection with the terms of this License Agreement. Any previous or subsequent license granted to You for use of the Software shall be governed by the terms and conditions of the agreement entered in connection with purchase of that version of the Software. You agree that you will comply with all applicable laws and regulations with respect to the Software, including without limitation all export and re-export control laws and regulations. While redistributing the Software or Modifications thereof, You may choose to offer acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License Agreement. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on Our behalf. You agree to indemnify, defend, and hold Us harmless from and against any liability incurred by, or claims asserted against, Us (i) by reason of Your accepting any such support, warranty, indemnity or additional liability; or (ii) arising out of the use, reproduction or distribution of Your Application, except to the extent such claim is solely based on the inclusion of the Software therein. You agree to be identified as a customer of ours and You agree that We may refer to You by name, trade name and trademark, if applicable, and may briefly describe Your business in our marketing materials and web site. You may not assign this License Agreement without Our prior written consent, which will not be unreasonably withheld. This License Agreement will inure to the benefit of Our successors and assigns. You acknowledge that this License Agreement is complete and is the exclusive representation of our agreement. No oral or written information given by Us or on our behalf shall create a warranty or collateral contract, or in any way increase the scope of this License Agreement in any way, and You may not rely on any such oral or written information. No term or condition contained in any purchase order shall apply unless expressly accepted by Us in writing. There are no implied licenses or other implied rights granted under this License Agreement, and all rights, save for those expressly granted hereunder, shall remain with Us and our licensors. In addition, no licenses or immunities are granted to the combination of the Software and/or Modifications, as applicable, with any other software or hardware not delivered by Us to You under this License Agreement. If any provision in this License Agreement shall be determined to be invalid, such provision shall be deemed omitted; the remainder of this License Agreement shall continue in full force and effect. If any remedy provided is determined to have failed for its essential purpose, all limitations of liability and exclusions of damages set forth in this License Agreement shall remain in effect. This License Agreement may be modified only by a written instrument signed by an authorized representative of each party. This License Agreement is governed by the law of the State of Oregon, United States (notwithstanding conflicts of laws provisions), and all parties irrevocably submit to the jurisdiction of the courts of the State of Oregon and further agree to commence any litigation which may arise hereunder in the state or federal courts located in the judicial district of Multnomah County, Oregon, US. If the Software or any related documentation is licensed to the U.S. government or any agency thereof, it will be deemed to be "commercial computer software" or "commercial computer software documentation", pursuant to DFAR Section 227.7202 and FAR Section 12.212. Any use of the Software or related documentation by the U.S. government will be governed solely by the terms of this License Agreement. ================================================ FILE: LICENSE_STANDARD ================================================ Standard End-user License Agreement =================================== THIS LICENSE AGREEMENT DESCRIBES YOUR RIGHTS WITH RESPECT TO THE SOFTWARE AND ITS COMPONENTS. 1. OWNERSHIP, LICENSE GRANT This is a license agreement and not an agreement for sale. We reserve ownership of all intellectual property rights inherent in or relating to the Software, which include, but are not limited to, all copyright, patent rights, all rights in relation to registered and unregistered trademarks (including service marks), confidential information (including trade secrets and know-how) and all rights other than those expressly granted by this License Agreement. Subject to the payment of the fee required and subject to the terms and conditions of this License Agreement, We grant to You a revocable, non- transferable and non-exclusive license (i) for Designated User(s) (as defined below) within Your organization to install and use the Software on any workstations used exclusively by such Designated User and (ii) for You to install and use the Software in connection with unlimited domains and sub-domains on unlimited servers, solely in connection with distribution of the Software in accordance with sections 3 and 4 below. This license is not sublicensable except as explicitly set forth herein. "Designated User(s)" shall mean Your employee(s) acting within the scope of their employment or Your consultant(s) or contractor(s) acting within the scope of the services they provide for You or on Your behalf for whom You have purchased a license to use the Software. 2. PERMITTED USES, SOURCE CODE, MODIFICATIONS We provide You with source code so that You can create Modifications of the original Software, where Modification means: a) any addition to or deletion from the contents of a file included in the original Software or previous Modifications created by You, or b) any new file that contains any part of the original Software or previous Modifications. While You retain all rights to any original work authored by You as part of the Modifications, We continue to own all copyright and other intellectual property rights in the Software. 3. DISTRIBUTION You may distribute the Software in any applications, frameworks, or elements (collectively referred to as an "Application" or "Applications") that you develop using the Software in accordance with this License Agreement, provided that such distribution does not violate the restrictions set forth in section 4 of this License Agreement. You must not remove, obscure or interfere with any copyright, acknowledgment, attribution, trademark, warning or disclaimer statement affixed to, incorporated in or otherwise applied in connection with the Software. You are required to ensure that the Software is not reused by or with any applications other than those with which You distribute it as permitted herein. For example, if You install the Software on a customer's server, that customer is not permitted to use the Software independently of Your application, and must be informed as such. You will not owe Us any royalties for Your distribution of the Software in accordance with this License Agreement. 4. PROHIBITED USES You may not, without Our prior written consent, redistribute the Software or Modifications other than by including the Software or a portion thereof within Your own product, which must have substantially different functionality than the Software or Modifications and must not allow any third party to use the Software or Modifications, or any portions thereof, for software development or application development purposes. You are explicitly not allowed to redistribute the Software or Modifications as part of any product that can be described as a development toolkit or library, an application builder, a website builder or any product that is intended for use by software, application, or website developers or designers. You may not change or remove the copyright notice from any of the files included in the Software or Modifications. You may not redistribute the Software as part of a product, "appliance" or "virtual server". You may not redistribute the Software on any server which is not directly under Your control. UNDER NO CIRCUMSTANCES MAY YOU USE THE SOFTWARE FOR A PRODUCT THAT IS INTENDED FOR SOFTWARE OR APPLICATION DEVELOPMENT PURPOSES. 5. TERMINATION This License Agreement and Your right to use the Software and Modifications will terminate immediately without notice if You fail to comply with the terms and conditions of this License Agreement. Upon termination, You agree to immediately cease using and destroy the Software or Modifications, including all accompanying documents. The provisions of sections 4, 5, 6, 7, 8, 9, 10 and 11 will survive any termination of this License Agreement. 6. DISCLAIMER OF WARRANTIES TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, WE AND OUR SUPPLIERS DISCLAIM ALL WARRANTIES AND CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT, WITH REGARD TO THE SOFTWARE. WE DO NOT GUARANTEE THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, AND YOU ACKNOWLEDGE THAT IT IS NOT TECHNICALLY PRACTICABLE FOR US TO DO SO. 7. LIMITATION OF LIABILITIES TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL WE OR OUR SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION OR ANY OTHER PECUNIARY LAW) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN ANY CASE, OUR ENTIRE LIABILITY UNDER ANY PROVISION OF THIS LICENSE AGREEMENT SHALL BE LIMITED TO THE AMOUNT ACTUALLY PAID BY YOU FOR THE SOFTWARE. 8. VERIFICATION We or a certified auditor acting on Our behalf, may, upon its reasonable request and at its expense, audit You with respect to the use of the Software. Such audit may be conducted by mail, electronic means or through an in-person visit to Your place of business. Any such in-person audit shall be conducted during regular business hours at Your facilities and shall not unreasonably interfere with Your business activities. We shall not remove, copy, or redistribute any electronic material during the course of an audit. If an audit reveals that You are using the Software in a way that is in material violation of the terms of the License Agreement, then You shall pay Our reasonable costs of conducting the audit. In the case of a material violation, You agree to pay Us any amounts owing that are attributable to the unauthorized use. In the alternative, We reserve the right, at Our sole option, to terminate the licenses for the Software. 9. THIRD PARTY SOFTWARE The Software may contain third party open-source software which requires notices and/or additional terms and conditions. Such required third party software notices and/or additional terms and conditions are located in the NOTICES file accompanying the Software distribution (also available at https://www.hangfire.io/licensing/third-party.html), and are made a part of and incorporated by reference into this Agreement. By accepting this Agreement, you are also accepting the additional terms and conditions, if any, set forth therein. 10. PAYMENT AND TAXES If credit has been extended to You by Us, all payments under this License Agreement are due within thirty (30) days of the date We mail an invoice to You. If We have not extended credit to You, You shall be required to make payment concurrent with the delivery of the Software by Us. All amounts payable are gross amounts but exclusive of any value added tax, use tax, sales tax or similar tax. You shall be entitled to withhold from payments any applicable withholding taxes and comply with all applicable tax and employment legislation. Each party shall pay all taxes (including, but not limited to, taxes based upon its income) or levies imposed on it under applicable laws, regulations and tax treaties as a result of this Agreement and any payments made hereunder (including those required to be withheld or deducted from payments). Each party shall furnish evidence of such paid taxes as is sufficient to enable the other party to obtain any credits available to it, including original withholding tax certificates. 11. MISCELLANEOUS The license granted herein applies only to the version of the Software available when purchased in connection with the terms of this License Agreement. Any previous or subsequent license granted to You for use of the Software shall be governed by the terms and conditions of the agreement entered in connection with purchase of that version of the Software. You agree that you will comply with all applicable laws and regulations with respect to the Software, including without limitation all export and re-export control laws and regulations. While redistributing the Software or Modifications thereof, You may choose to offer acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License Agreement. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on Our behalf. You agree to indemnify, defend, and hold Us harmless from and against any liability incurred by, or claims asserted against, Us (i) by reason of Your accepting any such support, warranty, indemnity or additional liability; or (ii) arising out of the use, reproduction or distribution of Your Application, except to the extent such claim is solely based on the inclusion of the Software therein. You agree to be identified as a customer of ours and You agree that We may refer to You by name, trade name and trademark, if applicable, and may briefly describe Your business in our marketing materials and web site. You may not assign this License Agreement without Our prior written consent, which will not be unreasonably withheld. This License Agreement will inure to the benefit of Our successors and assigns. You acknowledge that this License Agreement is complete and is the exclusive representation of our agreement. No oral or written information given by Us or on our behalf shall create a warranty or collateral contract, or in any way increase the scope of this License Agreement in any way, and You may not rely on any such oral or written information. No term or condition contained in any purchase order shall apply unless expressly accepted by Us in writing. There are no implied licenses or other implied rights granted under this License Agreement, and all rights, save for those expressly granted hereunder, shall remain with Us and our licensors. In addition, no licenses or immunities are granted to the combination of the Software and/or Modifications, as applicable, with any other software or hardware not delivered by Us to You under this License Agreement. If any provision in this License Agreement shall be determined to be invalid, such provision shall be deemed omitted; the remainder of this License Agreement shall continue in full force and effect. If any remedy provided is determined to have failed for its essential purpose, all limitations of liability and exclusions of damages set forth in this License Agreement shall remain in effect. This License Agreement may be modified only by a written instrument signed by an authorized representative of each party. This License Agreement is governed by the law of the State of Oregon, United States (notwithstanding conflicts of laws provisions), and all parties irrevocably submit to the jurisdiction of the courts of the State of Oregon and further agree to commence any litigation which may arise hereunder in the state or federal courts located in the judicial district of Multnomah County, Oregon, US. If the Software or any related documentation is licensed to the U.S. government or any agency thereof, it will be deemed to be "commercial computer software" or "commercial computer software documentation", pursuant to DFAR Section 227.7202 and FAR Section 12.212. Any use of the Software or related documentation by the U.S. government will be governed solely by the terms of this License Agreement. ================================================ FILE: NOTICES ================================================ Third Party Software Notices ============================ Hangfire products use software provided by third parties, including open source software. The following copyright statements and licenses apply to various components that are distributed with various Hangfire products. The Hangfire product that includes this file does not necessarily use all of the third party software components referred to below. Licensee must fully agree and comply with these license terms or must not use these components. The third party license terms apply only to the respective software to which the license pertains, and the third party license terms do not apply to the Hangfire software. In the event that we accidentally failed to list a required notice, please bring it to our attention by sending an email to . ### [LibLog](https://github.com/damianh/LibLog) (MIT License) Copyright (C) 2011-2017 Damian Hickey. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### [NCrontab](https://github.com/atifaziz/NCrontab) (Apache License 2.0) Copyright (c) 2008 Atif Aziz. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ### Microsoft.Owin (Apache License 2.0) Part of Katana Project (http://katanaproject.codeplex.com/) Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ### [Cron Expression Descriptor](https://github.com/bradymholt/cron-expression-descriptor) (MIT License) Copyright (c) 2013 Brady Holt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### [Stack Trace Parser](https://github.com/atifaziz/StackTraceParser) (Apache License 2.0) Copyright (c) 2011 Atif Aziz. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ### [Stack Trace Formatter](https://github.com/atifaziz/StackTraceFormatter) (Apache License 2.0) Copyright (c) 2011 Atif Aziz. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ### MoreLINQ - Extensions to LINQ to Objects (Apache License 2.0) Copyright (c) 2008 Jonathan Skeet. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ### [ExpressionUtil from ASP.NET MVC](https://github.com/aspnet/AspNetWebStack/tree/v3.0/src/Microsoft.Web.Mvc/ExpressionUtil) (Apache License 2.0) Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ### [Dapper](https://github.com/StackExchange/Dapper) (Apache License 2.0) Copyright (c) 2017 Stack Exchange, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ### [Bootstrap](https://github.com/twbs/bootstrap) (MIT License) Copyright 2011-2019 Twitter, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### [Moment.js](https://github.com/moment/moment) (MIT License) Copyright (c) JS Foundation and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### [jQuery](https://github.com/jquery/jquery) (MIT License) Copyright OpenJS Foundation and other contributors, https://openjsf.org/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### MSMQ API Extensions (MIT License) Copyright (c) 2014 Philip Hoppe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### [Chart.js](https://www.chartjs.org/) (MIT License) The MIT License (MIT) Copyright (c) 2018 Chart.js Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### [chartjs-plugin-streaming](https://github.com/nagix/chartjs-plugin-streaming) (MIT License) Copyright (c) 2021-2019 Akihiko Kusanagi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: NuGet.config ================================================ Microsoft;aspnet;dotnetframework;HangfireIO;xunit;jamesnk;kzu;castleproject;psake;ILRepack;davidebbo;StackExchange;Dapper;brady.holt;dwhelan;raboof;damianh; ================================================ FILE: README.md ================================================ Hangfire ========= [![Official Site](https://img.shields.io/badge/site-hangfire.io-blue.svg)](https://www.hangfire.io) [![Latest version](https://img.shields.io/nuget/v/Hangfire.svg?label=release)](https://www.nuget.org/packages?q=hangfire) [![Downloads](https://img.shields.io/nuget/dt/Hangfire.Core.svg)](https://www.nuget.org/packages/Hangfire.Core/) [![License LGPLv3](https://img.shields.io/badge/license-LGPLv3-green.svg)](https://www.gnu.org/licenses/lgpl-3.0.html) [![Coverity Scan](https://scan.coverity.com/projects/4423/badge.svg?flat=1)](https://scan.coverity.com/projects/hangfireio-hangfire) ## Build Status   | `main` | `dev` --- | --- | --- **AppVeyor** | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/70m632jkycqpnsp9/branch/main?svg=true)](https://ci.appveyor.com/project/HangfireIO/hangfire-525) | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/70m632jkycqpnsp9/branch/dev?svg=true)](https://ci.appveyor.com/project/HangfireIO/hangfire-525) ## Overview Incredibly easy way to perform **fire-and-forget**, **delayed** and **recurring jobs** in **.NET applications**. CPU and I/O intensive, long-running and short-running jobs are supported. No Windows Service / Task Scheduler required. Backed by Redis, SQL Server, SQL Azure and MSMQ. Hangfire provides a unified programming model to handle background tasks in a **reliable way** and run them on shared hosting, dedicated hosting or in cloud. You can start with a simple setup and grow computational power for background jobs with time for these scenarios: - mass notifications/newsletters - batch import from xml, csv or json - creation of archives - firing off web hooks - deleting users - building different graphs - image/video processing - purging temporary files - recurring automated reports - database maintenance - *…and so on* Hangfire is a .NET alternative to [Resque](https://github.com/resque/resque), [Sidekiq](https://sidekiq.org), [delayed_job](https://github.com/collectiveidea/delayed_job), [Celery](https://www.celeryproject.org). ![Hangfire Dashboard](https://www.hangfire.io/img/ui/dashboard-sm.png) Installation ------------- Hangfire is available as a NuGet package. You can install it using the NuGet Package Console window: ``` PM> Install-Package Hangfire ``` After installation, update your existing [OWIN Startup](https://www.asp.net/aspnet/overview/owin-and-katana/owin-startup-class-detection) file with the following lines of code. If you do not have this class in your project or don't know what is it, please read the [Quick start](https://docs.hangfire.io/en/latest/getting-started/index.html) guide to learn about how to install Hangfire. ```csharp public void Configuration(IAppBuilder app) { GlobalConfiguration.Configuration.UseSqlServerStorage(""); app.UseHangfireServer(); app.UseHangfireDashboard(); } ``` Usage ------ This is an incomplete list of features; to see all of them, check the [official site](https://www.hangfire.io) and the [documentation](https://docs.hangfire.io). [**Fire-and-forget tasks**](https://docs.hangfire.io/en/latest/background-methods/calling-methods-in-background.html) Dedicated worker pool threads execute queued background jobs as soon as possible, shortening your request's processing time. ```csharp BackgroundJob.Enqueue(() => Console.WriteLine("Simple!")); ``` [**Delayed tasks**](https://docs.hangfire.io/en/latest/background-methods/calling-methods-with-delay.html) Scheduled background jobs are executed only after a given amount of time. ```csharp BackgroundJob.Schedule(() => Console.WriteLine("Reliable!"), TimeSpan.FromDays(7)); ``` [**Recurring tasks**](https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html) Recurring jobs have never been simpler; just call the following method to perform any kind of recurring task using the [CRON expressions](https://en.wikipedia.org/wiki/Cron#CRON_expression). ```csharp RecurringJob.AddOrUpdate(() => Console.WriteLine("Transparent!"), Cron.Daily); ``` **Continuations** Continuations allow you to define complex workflows by chaining multiple background jobs together. ```csharp var id = BackgroundJob.Enqueue(() => Console.WriteLine("Hello, ")); BackgroundJob.ContinueWith(id, () => Console.WriteLine("world!")); ``` **Process background tasks inside a web application…** You can process background tasks in any OWIN-compatible application framework, including [ASP.NET MVC](https://www.asp.net/mvc), [ASP.NET Web API](https://www.asp.net/web-api), [FubuMvc](https://fubu-project.org), [Nancy](https://nancyfx.org), etc. Forget about [AppDomain unloads, Web Garden & Web Farm issues](https://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/) – Hangfire is reliable for web applications from scratch, even on shared hosting. ```csharp app.UseHangfireServer(); ``` **… or anywhere else** In console applications, Windows Service, Azure Worker Role, etc. ```csharp using (new BackgroundJobServer()) { Console.WriteLine("Hangfire Server started. Press ENTER to exit..."); Console.ReadLine(); } ``` Questions? Problems? --------------------- Open-source projects develop more smoothly when discussions are public. If you have any questions, problems related to Hangfire usage or if you want to discuss new features, please visit the [discussion forum](https://discuss.hangfire.io). You can sign in there using your existing Google or GitHub account, so it's very simple to start using it. If you've discovered a bug, please report it to the [Hangfire GitHub Issues](https://github.com/HangfireIO/Hangfire/issues?state=open). Detailed reports with stack traces, actual and expected behaviours are welcome. Related Projects ----------------- Please see the [Extensions](https://www.hangfire.io/extensions.html) page on the official site. Building the sources --------------------- Prerequisites: * [Razor Generator](https://marketplace.visualstudio.com/items?itemName=DavidEbbo.RazorGenerator): Required if you intend to edit the cshtml files. * Install the MSMQ service (Microsoft Message Queue Server), if not already installed. Then, create an environment variable with Variable name `Hangfire_SqlServer_ConnectionStringTemplate` and put your connection string in the Variable value field. Example: * Variable name: `Hangfire_SqlServer_ConnectionStringTemplate` * Variable value: `Data Source=.\sqlexpress;Initial Catalog=Hangfire.SqlServer.Tests;Integrated Security=True;` To build a solution and get assembly files, just run the following command. All build artifacts, including `*.pdb` files, will be placed into the `build` folder. **Before proposing a pull request, please use this command to ensure everything is ok.** Btw, you can execute this command from the Package Manager Console window. ``` build ``` To build NuGet packages as well as an archive file, use the `pack` command as shown below. You can find the result files in the `build` folder. ``` build pack ``` To see the full list of available commands, pass the `-docs` switch: ``` build -docs ``` Hangfire uses [psake](https://github.com/psake/psake) build automation tool. All psake tasks and functions defined in `psake-build.ps1` (for this project) and `psake-common.ps1` (for other Hangfire projects) files. Thanks to the psake project, they are very simple to use and modify! Razor templates are compiled upon save with the [Razor Generator Visual Studio extension](https://marketplace.visualstudio.com/items?itemName=DavidEbbo.RazorGenerator). You will need this installed if you want to modify the Dashboard UI. Reporting security issues -------------------------- In order to give the community time to respond and upgrade we strongly urge you report all security issues privately. Please email us at [security@hangfire.io](mailto:security@hangfire.io) with details and we will respond ASAP. Security issues always take precedence over bug fixes and feature work. We can and do mark releases as "urgent" if they contain serious security fixes. License -------- Copyright © 2013-2026 Hangfire OÜ. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses). Legal ------ By submitting a Pull Request, you disavow any rights or claims to any changes submitted to the Hangfire project and assign the copyright of those changes to Hangfire OÜ. If you cannot or do not want to reassign those rights (your employment contract for your employer may not allow this), you should not submit a PR. Open an issue and someone else can do the work. This is a legal way of saying "If you submit a PR to us, that code becomes ours". 99.9% of the time that's what you intend anyways; we hope it doesn't scare you away from contributing. ================================================ FILE: SECURITY.md ================================================ # Reporting security issues In order to give the community time to respond and upgrade we strongly urge you report all security issues privately. Please email us at [security@hangfire.io](security@hangfire.io) with details and we will respond ASAP. Security issues always take precedence over bug fixes and feature work. We can and do mark releases as "urgent" if they contain serious security fixes. ================================================ FILE: appveyor.yml ================================================ # AppVeyor CI build file, https://ci.appveyor.com/project/odinserj/hangfire # Notes: # - Minimal appveyor.yml file is an empty file. All sections are optional. # - Indent each level of configuration with 2 spaces. Do not use tabs! # - All section names are case-sensitive. # - Section names should be unique on each level. # Please don't edit it manually, use the `build.bat version` command instead. version: 1.8.23-build-0{build} image: - Visual Studio 2022 - Ubuntu2004 #---------------------------------# # environment configuration # #---------------------------------# # environment variables environment: Hangfire_SqlServer_ConnectionStringTemplate: Server=.\SQL2017;Database={0};User Id=sa;Password=Password12!;TrustServerCertificate=True;PoolBlockingPeriod=NeverBlock SIGNPATH_API_TOKEN: secure: nvG+jv/K3utFvpHGx/N6Glpv0Wdj0wfBSl8c/tkHbn2AIwGcNe2e4VSOkod7xVpC COVERITY_TOKEN: secure: r3yBqxgALySnCK9W6uiStqoadsqYtrWQolzxGDVKF74= COVERITY_EMAIL: secure: wf51HXCiUYxuTe+eo3uQOxqyptSLrH4IEqq0958Rmx8= # enable service required for tests services: - mssql2017 #---------------------------------# # build configuration # #---------------------------------# # Installing MSMQ manually to avoid "Cannot initialize 'msmq' service handler" error before_build: - cmd: powershell Import-Module ServerManager; Add-WindowsFeature MSMQ; net start msmq - pwsh: Install-PSResource -Name SignPath -TrustRepository - sh: nuget locals all -clear build_script: - pwsh: IF ($IsWindows -and ($env:APPVEYOR_SCHEDULED_BUILD -or $env:APPVEYOR_REPO_COMMIT_MESSAGE -like "*covscan*")) { .\coverity-scan.ps1 } - cmd: IF NOT DEFINED APPVEYOR_SCHEDULED_BUILD build.bat sign - sh: chmod +x build.sh; ./build.sh #---------------------------------# # tests configuration # #---------------------------------# test: off #---------------------------------# # artifacts configuration # #---------------------------------# artifacts: - path: 'build\**\*.nupkg' - path: 'build\**\*.zip' #---------------------------------# # deployment configuration # #---------------------------------# deploy: - provider: NuGet api_key: secure: eMIULftUVSY15jDNaQZYuEVn7MYcKWXiwlEt2x2Cir6qJPw47EfwH2k+BjaAzxUK on: appveyor_repo_tag: true ================================================ FILE: build.bat ================================================ @echo off .nuget\NuGet.exe restore .nuget\packages.config -OutputDirectory packages -UseLockFile -LockedMode -NoCache || exit /b 666 pwsh.exe -NoProfile -ExecutionPolicy RemoteSigned -Command "& {Import-Module '.\packages\psake.*\tools\psake.psm1'; invoke-psake .\psake-project.ps1 %*; if ($psake.build_success -eq $false) { exit 1 } else { exit 0 }; }" exit /B %errorlevel% ================================================ FILE: build.sh ================================================ #!/bin/bash set -e; export Hangfire_SqlServer_ConnectionStringTemplate="Server=tcp:127.0.0.1,1433;Database={0};User Id=sa;Password=Password12!;TrustServerCertificate=True;PoolBlockingPeriod=NeverBlock"; if hash dotnet 2>/dev/null; then dotnet test -c Release -f netcoreapp3.1 tests/Hangfire.Core.Tests; dotnet test -c Release -f net6.0 tests/Hangfire.Core.Tests; if hash sqlcmd 2>/dev/null; then dotnet test -c Release -f netcoreapp3.1 tests/Hangfire.SqlServer.Tests; dotnet test -c Release -f net6.0 tests/Hangfire.SqlServer.Tests; fi fi ================================================ FILE: coverity-scan.ps1 ================================================ cov-configure --cs cov-build.exe --dir cov-int build.bat compile # Compress results. "Compressing Coverity results..." $zipEncoderDef = @' namespace AnalyseCode { public class PortableFileNameEncoder: System.Text.UTF8Encoding { public PortableFileNameEncoder() {} public override byte[] GetBytes(string entry) { return base.GetBytes(entry.Replace("\\", "/")); } } } '@ Add-Type -TypeDefinition $zipEncoderDef [IO.Compression.ZipFile]::CreateFromDirectory( "$env:APPVEYOR_BUILD_FOLDER\cov-int", "$env:APPVEYOR_BUILD_FOLDER\$env:APPVEYOR_PROJECT_NAME.zip", [IO.Compression.CompressionLevel]::Optimal, $true, # include root directory (New-Object AnalyseCode.PortableFileNameEncoder)) # Upload results to Coverity server. "Uploading Coverity results..." Add-Type -AssemblyName "System.Net.Http" $client = New-Object Net.Http.HttpClient $client.Timeout = [TimeSpan]::FromMinutes(20) $form = New-Object Net.Http.MultipartFormDataContent # Fill token field. [Net.Http.HttpContent]$formField = New-Object Net.Http.StringContent($env:COVERITY_TOKEN) $form.Add($formField, '"token"') # Fill email field. $formField = New-Object Net.Http.StringContent($env:COVERITY_EMAIL) $form.Add($formField, '"email"') # Fill file field. $fs = New-Object IO.FileStream( "$env:APPVEYOR_BUILD_FOLDER\$env:APPVEYOR_PROJECT_NAME.zip", [IO.FileMode]::Open, [IO.FileAccess]::Read) $formField = New-Object Net.Http.StreamContent($fs) $form.Add($formField, '"file"', "$env:APPVEYOR_PROJECT_NAME.zip") # Fill version field. $formField = New-Object Net.Http.StringContent($env:APPVEYOR_BUILD_VERSION) $form.Add($formField, '"version"') # Fill description field. $formField = New-Object Net.Http.StringContent("AppVeyor scheduled build.") $form.Add($formField, '"description"') # Submit form. $url = "https://scan.coverity.com/builds?project=$env:APPVEYOR_REPO_NAME" $task = $client.PostAsync($url, $form) try { $task.Wait() # throws AggregateException on timeout } catch [AggregateException] { throw $_.Exception.InnerException } $task.Result $fs.Close() ================================================ FILE: nuspecs/Hangfire.AspNetCore.nuspec ================================================ Hangfire.AspNetCore %version% Hangfire ASP.NET Core Support Sergey Odinokov HangfireIO, odinserj https://www.hangfire.io/ LICENSE.md icon.png ASP.NET Core support for Hangfire, a background job framework for .NET applications. Copyright © 2017-2026 Hangfire OÜ hangfire aspnetcore ================================================ FILE: nuspecs/Hangfire.Core.nuspec ================================================ Hangfire.Core %version% Hangfire Core Components Sergey Odinokov HangfireIO, odinserj https://www.hangfire.io/ LICENSE.md icon.png README.md An easy way to perform fire-and-forget, delayed and recurring tasks in .NET applications. No Windows Service required. An easy and reliable way to perform fire-and-forget, delayed and recurring, long-running, short-running, CPU or I/O intensive tasks in .NET applications. No Windows Service / Task Scheduler required. Backed by Redis, SQL Server, SQL Azure or MSMQ. This is a .NET alternative to Sidekiq, Resque and Celery. https://www.hangfire.io/ Copyright © 2013-2026 Hangfire OÜ Hangfire OWIN Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues ================================================ FILE: nuspecs/Hangfire.NetCore.nuspec ================================================ Hangfire.NetCore %version% Hangfire .NET Core's Worker Service Support Sergey Odinokov HangfireIO, odinserj https://www.hangfire.io/ LICENSE.md icon.png .NET Core's Worker Service host support for Hangfire, a background job framework for .NET applications. Copyright © 2019-2026 Hangfire OÜ hangfire netcore ================================================ FILE: nuspecs/Hangfire.SqlServer.MSMQ.nuspec ================================================ Hangfire.SqlServer.MSMQ %version% Hangfire MSMQ Queues for SQL Server Storage Sergey Odinokov HangfireIO, odinserj https://www.hangfire.io/ LICENSE.md icon.png MSMQ queues support for SQL Server job storage implementation for Hangfire, a background job framework for .NET applications. Copyright © 2014-2026 Hangfire OÜ Hangfire SqlServer MSMQ ================================================ FILE: nuspecs/Hangfire.SqlServer.nuspec ================================================ Hangfire.SqlServer %version% Hangfire SQL Server Storage Sergey Odinokov HangfireIO, odinserj https://www.hangfire.io/ LICENSE.md icon.png SQL Server 2008+ (including Express), SQL Server LocalDB and SQL Azure storage support for Hangfire, a background job framework for .NET applications. Copyright © 2013-2026 Hangfire OÜ Hangfire SqlServer SqlAzure LocalDB ================================================ FILE: nuspecs/Hangfire.nuspec ================================================ Hangfire %version% Hangfire Sergey Odinokov HangfireIO, odinserj https://www.hangfire.io/ LICENSE.md icon.png README.md An easy way to perform fire-and-forget, delayed and recurring tasks inside ASP.NET applications. No Windows Service required. An easy and reliable way to perform fire-and-forget, delayed and recurring, long-running, short-running, CPU or I/O intensive tasks inside ASP.NET applications. No Windows Service / Task Scheduler required. Even ASP.NET is not required. Backed by Redis, SQL Server, SQL Azure or MSMQ. This is a .NET alternative to Sidekiq, Resque and Celery. https://www.hangfire.io/ Copyright © 2013-2026 Hangfire OÜ Hangfire AspNet MVC AspNetCore NetCore SqlServer Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues ================================================ FILE: psake-project.ps1 ================================================ Include "packages\Hangfire.Build.0.5.0\tools\psake-common.ps1" Task Default -Depends Pack Task Merge -Depends Compile -Description "Run ILRepack /internalize to merge required assemblies." { Repack-Assembly @("Hangfire.Core", "net451") @("Cronos", "CronExpressionDescriptor", "Microsoft.Owin") Repack-Assembly @("Hangfire.Core", "net46") @("Cronos", "CronExpressionDescriptor", "Microsoft.Owin") Repack-Assembly @("Hangfire.SqlServer", "net451") @("Dapper") Repack-Assembly @("Hangfire.Core", "netstandard1.3") @("Cronos") Repack-Assembly @("Hangfire.Core", "netstandard2.0") @("Cronos") Repack-Assembly @("Hangfire.SqlServer", "netstandard1.3") @("Dapper") Repack-Assembly @("Hangfire.SqlServer", "netstandard2.0") @("Dapper") } Task Test -Depends Merge -Description "Run unit and integration tests against merged assemblies." { # Dependencies shouldn't be re-built, because we need to run tests against merged assemblies to test # the same assemblies that are distributed to users. Since the `dotnet test` command doesn't support # the `--no-dependencies` command directly, we need to re-build tests themselves first. Exec { ls "tests\**\*.csproj" | % { dotnet build -c Release --no-restore --no-dependencies $_.FullName } } # We are running unit test project one by one, because pipelined version like the line above does not # support halting the whole execution pipeline when "dotnet test" command fails due to a failed test, # silently allowing build process to continue its execution even with failed tests. Exec { dotnet test -c Release --no-build "tests\Hangfire.Core.Tests" } Exec { dotnet test -c Release --no-build "tests\Hangfire.SqlServer.Tests" } Exec { dotnet test -c Release --no-build -p:TestTfmsInParallel=false "tests\Hangfire.SqlServer.Msmq.Tests" } } Task Collect -Depends Test -Description "Copy all artifacts to the build folder." { Collect-Assembly "Hangfire.Core" "net451" Collect-Assembly "Hangfire.SqlServer" "net451" Collect-Assembly "Hangfire.SqlServer.Msmq" "net451" Collect-Assembly "Hangfire.NetCore" "net451" Collect-Assembly "Hangfire.AspNetCore" "net451" Collect-Assembly "Hangfire.Core" "net46" Collect-Assembly "Hangfire.Core" "netstandard1.3" Collect-Assembly "Hangfire.SqlServer" "netstandard1.3" Collect-Assembly "Hangfire.NetCore" "netstandard1.3" Collect-Assembly "Hangfire.AspNetCore" "netstandard1.3" Collect-Assembly "Hangfire.Core" "netstandard2.0" Collect-Assembly "Hangfire.SqlServer" "netstandard2.0" Collect-Assembly "Hangfire.AspNetCore" "netstandard2.0" Collect-Assembly "Hangfire.NetCore" "netstandard2.0" Collect-Assembly "Hangfire.NetCore" "net461" Collect-Assembly "Hangfire.AspNetCore" "net461" Collect-Assembly "Hangfire.AspNetCore" "netcoreapp3.0" Collect-Assembly "Hangfire.NetCore" "netstandard2.1" Collect-Tool "src\Hangfire.SqlServer\DefaultInstall.sql" Collect-Localizations "Hangfire.Core" "net451" Collect-Localizations "Hangfire.Core" "net46" Collect-Localizations "Hangfire.Core" "netstandard1.3" Collect-Localizations "Hangfire.Core" "netstandard2.0" Collect-File "README.md" Collect-File "LICENSE.md" Collect-File "NOTICES" Collect-File "COPYING.LESSER" Collect-File "COPYING" Collect-File "LICENSE_STANDARD" Collect-File "LICENSE_ROYALTYFREE" } Task Pack -Depends Collect -Description "Create NuGet packages and archive files." { $version = Get-PackageVersion Create-Package "Hangfire" $version Create-Package "Hangfire.Core" $version Create-Package "Hangfire.SqlServer" $version Create-Package "Hangfire.SqlServer.Msmq" $version Create-Package "Hangfire.AspNetCore" $version Create-Package "Hangfire.NetCore" $version Create-Archive "Hangfire-$version" } Task Sign -Depends Pack -Description "Sign artifacts." { $version = Get-PackageVersion Sign-ArchiveContents "Hangfire-$version" "hangfire" } ================================================ FILE: samples/ConsoleSample/ConsoleSample.csproj ================================================  Exe net451 true full ================================================ FILE: samples/ConsoleSample/GenericServices.cs ================================================ using System; namespace ConsoleSample { public class GenericServices { public void Method(TType arg1, TMethod arg2) { Console.WriteLine($"Arg1: {arg1}, Arg2: {arg2}"); } } } ================================================ FILE: samples/ConsoleSample/NewFeatures.cs ================================================ using System; using Hangfire; namespace ConsoleSample { public static class NewFeatures { [AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)] public static bool TryExceptional(bool throwException) { if (throwException) throw new InvalidOperationException(); return true; } public static void Continuation([FromResult] bool result) { Console.WriteLine("Success continuation, result: " + result); } public static void CatchExceptional([FromException] ExceptionInfo exception) { if (exception.Type.Contains("OperationCanceledException")) { Console.WriteLine("Failure continuation: Operation was canceled"); } else { Console.WriteLine("Failure continuation: " + exception); } } public static void FinallyExceptional([FromException] ExceptionInfo exception) { if (exception == null) { Console.WriteLine("Finally clause, after success"); } else { Console.WriteLine("Finally clause, after failure: " + exception); } } public static void FinallyExceptional2([FromResult] bool result, [FromException] ExceptionInfo exception) { if (exception == null) { Console.WriteLine("Finally clause 2, after success: " + result); } else { Console.WriteLine("Finally clause 2, after failure: " + exception); } } public static void Test(bool throwException) { var exceptionalId = BackgroundJob.Enqueue(() => TryExceptional(throwException)); BackgroundJob.ContinueJobWith(exceptionalId, () => Continuation(default), JobContinuationOptions.OnlyOnSucceededState); BackgroundJob.ContinueJobWith(exceptionalId, () => CatchExceptional(default), JobContinuationOptions.OnlyOnDeletedState); BackgroundJob.ContinueJobWith(exceptionalId, () => FinallyExceptional(default), JobContinuationOptions.OnlyOnSucceededState | JobContinuationOptions.OnlyOnDeletedState); BackgroundJob.ContinueJobWith(exceptionalId, () => FinallyExceptional2(default, default), JobContinuationOptions.OnAnyFinishedState); } } } ================================================ FILE: samples/ConsoleSample/Program.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Hangfire; using Hangfire.Common; using Hangfire.Dashboard; using Hangfire.SqlServer; using Hangfire.States; using Microsoft.Owin.Hosting; namespace ConsoleSample { public static class Program { public static void Main() { GlobalConfiguration.Configuration .UseColouredConsoleLogProvider() .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseIgnoredAssemblyVersionTypeResolver() .UseRecommendedSerializerSettings() .UseResultsInContinuations() .UseJobDetailsRenderer(10, dto => throw new InvalidOperationException()) .UseJobDetailsRenderer(10, dto => new NonEscapedString("

Hello, world!

")) .UseDefaultCulture(CultureInfo.CurrentCulture) .UseSqlServerStorage(@"Server=.\;Database=Hangfire.Sample;Trusted_Connection=True;", new SqlServerStorageOptions { EnableHeavyMigrations = true, DisableTransactionScope = true }); Console.WriteLine(SerializationHelper.Serialize(new ExceptionInfo(new OperationCanceledException()), SerializationOption.Internal)); var backgroundJobs = new BackgroundJobClient(); backgroundJobs.RetryAttempts = 5; NewFeatures.Test(throwException: false); NewFeatures.Test(throwException: true); var job1 = BackgroundJob.Enqueue(x => x.WriteIndex(0)); var job2 = BackgroundJob.ContinueJobWith(job1, "default", x => x.WriteIndex(default)); var job3 = BackgroundJob.ContinueJobWith(job2, "critical", x => x.WriteIndex(default)); var job4 = BackgroundJob.ContinueJobWith(job3, "default", x => x.WriteIndex(default)); var job5 = BackgroundJob.ContinueJobWith(job4, "critical", x => x.WriteIndex(default)); RecurringJob.AddOrUpdate("seconds", () => Console.WriteLine("Hello, seconds!"), "*/15 * * * * *"); RecurringJob.AddOrUpdate("Console.WriteLine", () => Console.WriteLine("Hello, world!"), Cron.Minutely); RecurringJob.AddOrUpdate("hourly", () => Console.WriteLine("Hello"), "25 15 * * *"); RecurringJob.AddOrUpdate("neverfires", () => Console.WriteLine("Can only be triggered"), "0 0 31 2 *"); RecurringJob.AddOrUpdate("Hawaiian", () => Console.WriteLine("Hawaiian"), "15 08 * * *", new RecurringJobOptions { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time") }); RecurringJob.AddOrUpdate("UTC", "critical", () => Console.WriteLine("UTC"), "15 18 * * *"); RecurringJob.AddOrUpdate("Russian", () => Console.WriteLine("Russian"), "15 21 * * *", new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); using (WebApp.Start("http://localhost:12345")) { var count = 1; while (true) { var command = Console.ReadLine(); if (command == null || command.Equals("stop", StringComparison.OrdinalIgnoreCase)) { break; } if (command.StartsWith("add", StringComparison.OrdinalIgnoreCase)) { try { var workCount = int.Parse(command.Substring(4)); for (var i = 0; i < workCount; i++) { var number = i; BackgroundJob.Enqueue(x => x.Random(number)); } Console.WriteLine("Jobs enqueued."); } catch (Exception ex) { Console.WriteLine(ex.Message); } } if (command.StartsWith("async", StringComparison.OrdinalIgnoreCase)) { try { var workCount = int.Parse(command.Substring(6)); for (var i = 0; i < workCount; i++) { BackgroundJob.Enqueue(x => x.Async(CancellationToken.None)); } Console.WriteLine("Jobs enqueued."); } catch (Exception ex) { Console.WriteLine(ex.Message); } } if (command.StartsWith("static", StringComparison.OrdinalIgnoreCase)) { try { var workCount = int.Parse(command.Substring(7)); for (var i = 0; i < workCount; i++) { BackgroundJob.Enqueue(() => Console.WriteLine("Hello, {0}!", "world")); } Console.WriteLine("Jobs enqueued."); } catch (Exception ex) { Console.WriteLine(ex.Message); } } if (command.StartsWith("error", StringComparison.OrdinalIgnoreCase)) { var workCount = int.Parse(command.Substring(6)); for (var i = 0; i < workCount; i++) { BackgroundJob.Enqueue(x => x.Error()); } } if (command.StartsWith("args", StringComparison.OrdinalIgnoreCase)) { var workCount = int.Parse(command.Substring(5)); for (var i = 0; i < workCount; i++) { BackgroundJob.Enqueue(x => x.Args(Guid.NewGuid().ToString(), 14442, DateTime.UtcNow)); } } if (command.StartsWith("custom", StringComparison.OrdinalIgnoreCase)) { var workCount = int.Parse(command.Substring(7)); for (var i = 0; i < workCount; i++) { BackgroundJob.Enqueue(x => x.Custom( new Random().Next(), new[] { "Hello", "world!" }, new Services.CustomObject { Id = 123 }, DayOfWeek.Friday )); } } if (command.StartsWith("fullargs", StringComparison.OrdinalIgnoreCase)) { var workCount = int.Parse(command.Substring(9)); for (var i = 0; i < workCount; i++) { BackgroundJob.Enqueue(x => x.FullArgs( false, 123, 'c', DayOfWeek.Monday, "hello", new TimeSpan(12, 13, 14), new DateTime(2012, 11, 10), new Services.CustomObject { Id = 123 }, new[] { "1", "2", "3" }, new[] { 4, 5, 6 }, new long[0], null, new List { "7", "8", "9" })); } } if (command.StartsWith("in", StringComparison.OrdinalIgnoreCase)) { var seconds = int.Parse(command.Substring(2)); var number = count++; BackgroundJob.Schedule("default", x => x.Random(number), TimeSpan.FromSeconds(seconds)); } if (command.StartsWith("cancelable", StringComparison.OrdinalIgnoreCase)) { var iterations = int.Parse(command.Substring(11)); BackgroundJob.Enqueue(x => x.Cancelable(iterations, JobCancellationToken.Null)); } if (command.StartsWith("delete", StringComparison.OrdinalIgnoreCase)) { var workCount = int.Parse(command.Substring(7)); var client = new BackgroundJobClient(); for (var i = 0; i < workCount; i++) { client.Create(x => x.EmptyDefault(), new DeletedState(new ExceptionInfo(new OperationCanceledException()))); } } if (command.StartsWith("fast", StringComparison.OrdinalIgnoreCase)) { try { var workCount = int.Parse(command.Substring(5)); Parallel.For(0, workCount, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, i => { BackgroundJob.Enqueue( i % 2 == 0 ? "critical" : "default", x => x.EmptyDefault()); }); Console.WriteLine("Jobs enqueued."); } catch (Exception ex) { Console.WriteLine(ex.Message); } } if (command.StartsWith("generic", StringComparison.OrdinalIgnoreCase)) { BackgroundJob.Enqueue>(x => x.Method("hello", 1)); } if (command.StartsWith("continuations", StringComparison.OrdinalIgnoreCase)) { WriteString("Hello, Hangfire continuations!"); } } } Console.WriteLine("Press Enter to exit..."); Console.ReadLine(); } public static void WriteString(string value) { var lastId = BackgroundJob.Enqueue(x => x.Write(value[0])); for (var i = 1; i < value.Length; i++) { lastId = BackgroundJob.ContinueJobWith(lastId, x => x.Write(value[i])); } BackgroundJob.ContinueJobWith(lastId, x => x.WriteBlankLine()); } } } ================================================ FILE: samples/ConsoleSample/Services.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Threading; using System.Threading.Tasks; using Hangfire; using Hangfire.States; namespace ConsoleSample { public class Services { private static readonly Random Rand = new Random(); public async Task WriteIndex([FromResult] int? index) { if (index == null) throw new ArgumentNullException(nameof(index)); Console.Write("Hello, world!\r\n"[index.Value]); await Task.Yield(); return index.Value + 1; } public async Task EmptyDefault() { await Task.Yield(); } public async Task Async(CancellationToken cancellationToken) { await Task.Yield(); await Task.Delay(TimeSpan.FromSeconds(20), cancellationToken); } [Obsolete("Please use EmptyDefault method instead with `critical` queue directly")] [Queue("critical")] public async Task EmptyCritical() { await Task.Yield(); } [AutomaticRetry(Attempts = 0), LatencyTimeout(30)] public async Task Error() { await Task.Yield(); Console.WriteLine("Beginning error task..."); throw new InvalidOperationException(null, new FileLoadException()); } [Queue("critical")] public async Task Random([FromResult] int? number) { int time; lock (Rand) { time = Rand.Next(10); } if (time < 5) { throw new Exception(); } await Task.Delay(TimeSpan.FromSeconds(5 + time)); Console.WriteLine("Finished task: " + number); return time; } public async Task Cancelable(int iterationCount, IJobCancellationToken token) { try { for (var i = 1; i <= iterationCount; i++) { await Task.Delay(1000); Console.WriteLine("Performing step {0} of {1}...", i, iterationCount); token.ThrowIfCancellationRequested(); } } catch (OperationCanceledException) { Console.WriteLine("Cancellation requested, exiting..."); throw; } } [DisplayName("Name: {0}")] public async Task Args(string name, int authorId, DateTime createdAt) { await Task.Yield(); Console.WriteLine($"{name}, {authorId}, {createdAt}"); } public async Task Custom(int id, string[] values, CustomObject objects, DayOfWeek dayOfWeek) { await Task.Yield(); } public async Task FullArgs( bool b, int i, char c, DayOfWeek e, string s, TimeSpan t, DateTime d, CustomObject o, string[] sa, int[] ia, long[] ea, object[] na, List sl) { await Task.Yield(); } public async Task LongRunning(IJobCancellationToken token) { await Task.Delay(TimeSpan.FromMinutes(30), token.ShutdownToken); } public class CustomObject { public int Id { get; set; } public CustomObject[] Children { get; set; } } public async Task Write(char character) { await Task.Yield(); Console.Write(character); } public async Task WriteBlankLine() { await Task.Yield(); Console.WriteLine(); } [IdempotentCompletion] public static async Task WriteLine(string value) { await Task.Yield(); Console.WriteLine(value); return new AwaitingState("asfafs", new EnqueuedState("criticalll")); } } } ================================================ FILE: samples/ConsoleSample/Startup.cs ================================================ using System; using Hangfire; using Owin; namespace ConsoleSample { public class Startup { public void Configuration(IAppBuilder app) { app.UseErrorPage(); app.UseHangfireDashboard(String.Empty); app.UseHangfireServer(new BackgroundJobServerOptions { Queues = new[] { "critical", "default" }, TaskScheduler = null, SchedulePollingInterval = TimeSpan.FromSeconds(1) }); } } } ================================================ FILE: samples/ConsoleSample/app.config ================================================ ================================================ FILE: samples/ConsoleSample/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.1": { "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net451": "1.0.3" } }, "Microsoft.Owin.SelfHost": { "type": "Direct", "requested": "[4.0.0, )", "resolved": "4.0.0", "contentHash": "Gsxjg466eKBykIMZyrPP1IA8MIbYPd/1gcSWc6JLGb+CUO/elctCJomSbOJFceh9YU9hj9qURjpx0ns6/AghSA==", "dependencies": { "Microsoft.Owin": "4.0.0", "Microsoft.Owin.Diagnostics": "4.0.0", "Microsoft.Owin.Host.HttpListener": "4.0.0", "Microsoft.Owin.Hosting": "4.0.0", "Owin": "1.0.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.2, )", "resolved": "13.0.2", "contentHash": "R2pZ3B0UjeyHShm9vG+Tu0EBb2lC8b0dFzV9gVn50ofHXh9Smjk6kTn7A/FdAsC8B5cKib1OnGYOXxRBz5XQDg==" }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Dapper": { "type": "Transitive", "resolved": "1.60.6", "contentHash": "mmnJNhKMeF2KhvVXDoVQlFxre8aJAo71YBJrKqFlvuqzYC2QiXUq94/GCDBJzU7paq4GqpkV2glw3308TcGibw==" }, "Microsoft.NETFramework.ReferenceAssemblies.net451": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "vVPinxdLrwoX81ApbNIHDBI6qymQEy8eSOxDNBgKJtc2+cifnF0oT1U2d3EFx+V5O68yaqna2myZJNsgKCpVkA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.Owin.Diagnostics": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "96t+ud+Ai2kPnkdThUPL8wM9C1MAzZ80zlfTvjK0VwJsy+9F2rylFrsRx+dD7KSsN+ANKAuxSQlaYuzU9O8pZA==", "dependencies": { "Microsoft.Owin": "4.0.0", "Owin": "1.0.0" } }, "Microsoft.Owin.Host.HttpListener": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "AC0fcRPFg/gf6AmATmCJBGnD14URhCYOHtAW0g248YzZ4ByIkVA7jHBQbfij+zyIOeOya8uaAwFPCIN1wyiOQw==" }, "Microsoft.Owin.Hosting": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "rAkxeyiIliRbMZyPxo83QE+UY7HNTsFOH9+wqQTcQyWBVbYkBQp373meNt4+DpupMB56A1BopLDeyoX9DZJrbA==", "dependencies": { "Microsoft.Owin": "4.0.0", "Owin": "1.0.0" } }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[1.60.6, )", "Hangfire.Core": "[1.0.0, )" } }, "hangfire.sqlserver.msmq": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Hangfire.SqlServer": "[1.0.0, )" } } }, ".NETFramework,Version=v4.5.1/win7-x86": {} } } ================================================ FILE: samples/NetCoreSample/NetCoreSample.csproj ================================================  Exe net6.0 latest NU1701;NU1702 false ================================================ FILE: samples/NetCoreSample/Program.cs ================================================ using System; using System.Data; using System.Threading; using System.Threading.Tasks; using Hangfire; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.Server; using Hangfire.SqlServer; using Hangfire.States; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace NetCoreSample { class Program { static async Task Main(string[] args) { var host = new HostBuilder() .ConfigureLogging(x => x.AddConsole().SetMinimumLevel(LogLevel.Information)) .ConfigureServices((hostContext, services) => { services.Configure(option => { option.ShutdownTimeout = TimeSpan.FromSeconds(60); }); services.TryAddSingleton(new SqlServerStorageOptions { CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.FromTicks(1), UseRecommendedIsolationLevel = true, SlidingInvisibilityTimeout = TimeSpan.FromMinutes(1) }); services.TryAddSingleton(x => new CustomBackgroundJobFactory( new BackgroundJobFactory(x.GetRequiredService()))); services.TryAddSingleton(x => new CustomBackgroundJobPerformer( new BackgroundJobPerformer( x.GetRequiredService(), x.GetRequiredService(), TaskScheduler.Default))); services.TryAddSingleton(x => new CustomBackgroundJobStateChanger( new BackgroundJobStateChanger(x.GetRequiredService()))); services.AddHangfire((provider, configuration) => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) .UseSimpleAssemblyNameTypeSerializer() .UseSqlServerStorage( @"Server=.\;Database=Hangfire.Sample;Trusted_Connection=True;", provider.GetRequiredService())); services.AddHostedService(); services.AddHangfireServer(options => { options.StopTimeout = TimeSpan.FromSeconds(15); options.ShutdownTimeout = TimeSpan.FromSeconds(30); }); }) .Build(); await host.RunAsync(); } } internal class CustomBackgroundJobFactory : IBackgroundJobFactory { private readonly IBackgroundJobFactory _inner; public CustomBackgroundJobFactory([NotNull] IBackgroundJobFactory inner) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); } public IStateMachine StateMachine => _inner.StateMachine; public BackgroundJob Create(CreateContext context) { Console.WriteLine($"Create: {context.Job.Type.FullName}.{context.Job.Method.Name} in {context.InitialState?.Name} state"); return _inner.Create(context); } } internal class CustomBackgroundJobPerformer : IBackgroundJobPerformer { private readonly IBackgroundJobPerformer _inner; public CustomBackgroundJobPerformer([NotNull] IBackgroundJobPerformer inner) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); } public object Perform(PerformContext context) { Console.WriteLine($"Perform {context.BackgroundJob.Id} ({context.BackgroundJob.Job.Type.FullName}.{context.BackgroundJob.Job.Method.Name})"); return _inner.Perform(context); } } internal class CustomBackgroundJobStateChanger : IBackgroundJobStateChanger { private readonly IBackgroundJobStateChanger _inner; public CustomBackgroundJobStateChanger([NotNull] IBackgroundJobStateChanger inner) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); } public IState ChangeState(StateChangeContext context) { Console.WriteLine($"ChangeState {context.BackgroundJobId} to {context.NewState}"); return _inner.ChangeState(context); } } internal class RecurringJobsService : BackgroundService { private readonly IBackgroundJobClient _backgroundJobs; private readonly IRecurringJobManager _recurringJobs; private readonly ILogger _logger; public RecurringJobsService( [NotNull] IBackgroundJobClient backgroundJobs, [NotNull] IRecurringJobManager recurringJobs, [NotNull] ILogger logger) { _backgroundJobs = backgroundJobs ?? throw new ArgumentNullException(nameof(backgroundJobs)); _recurringJobs = recurringJobs ?? throw new ArgumentNullException(nameof(recurringJobs)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } protected override Task ExecuteAsync(CancellationToken stoppingToken) { try { _recurringJobs.AddOrUpdate("seconds", () => Console.WriteLine("Hello, seconds!"), "*/15 * * * * *"); _recurringJobs.AddOrUpdate("minutely", () => Console.WriteLine("Hello, world!"), Cron.Minutely); _recurringJobs.AddOrUpdate("hourly", () => Console.WriteLine("Hello"), "25 15 * * *"); _recurringJobs.AddOrUpdate("neverfires", () => Console.WriteLine("Can only be triggered"), "0 0 31 2 *"); _recurringJobs.AddOrUpdate("Hawaiian", () => Console.WriteLine("Hawaiian"), "15 08 * * *", new RecurringJobOptions { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time") }); _recurringJobs.AddOrUpdate("UTC", () => Console.WriteLine("UTC"), "15 18 * * *"); _recurringJobs.AddOrUpdate("Russian", () => Console.WriteLine("Russian"), "15 21 * * *", new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); } catch (Exception e) { _logger.LogError(e, "An exception occurred while creating recurring jobs."); } return Task.CompletedTask; } } } ================================================ FILE: samples/NetCoreSample/packages.lock.json ================================================ { "version": 1, "dependencies": { "net6.0": { "Microsoft.Extensions.Hosting": { "type": "Direct", "requested": "[8.0.1, )", "resolved": "8.0.1", "contentHash": "bP9EEkHBEfjgYiG8nUaXqMk/ujwJrffOkNPP7onpRMO8R+OUSESSP4xHkCAXgYZ1COP2Q9lXlU5gkMFh20gRuw==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.Configuration.Binder": "8.0.2", "Microsoft.Extensions.Configuration.CommandLine": "8.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "8.0.0", "Microsoft.Extensions.Configuration.FileExtensions": "8.0.1", "Microsoft.Extensions.Configuration.Json": "8.0.1", "Microsoft.Extensions.Configuration.UserSecrets": "8.0.1", "Microsoft.Extensions.DependencyInjection": "8.0.1", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Diagnostics": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", "Microsoft.Extensions.FileProviders.Physical": "8.0.0", "Microsoft.Extensions.Hosting.Abstractions": "8.0.1", "Microsoft.Extensions.Logging": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2", "Microsoft.Extensions.Logging.Configuration": "8.0.1", "Microsoft.Extensions.Logging.Console": "8.0.1", "Microsoft.Extensions.Logging.Debug": "8.0.1", "Microsoft.Extensions.Logging.EventLog": "8.0.1", "Microsoft.Extensions.Logging.EventSource": "8.0.1", "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", "requested": "[8.0.1, )", "resolved": "8.0.1", "contentHash": "uzcg/5U2eLyn5LIKlERkdSxw6VPC1yydnOSQiRRWGBGN3kphq3iL4emORzrojScDmxRhv49gp5BI8U3Dz7y4iA==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Logging": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2", "Microsoft.Extensions.Logging.Configuration": "8.0.1", "Microsoft.Extensions.Options": "8.0.2", "System.Runtime.CompilerServices.Unsafe": "6.0.0", "System.Text.Json": "8.0.5" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Dapper": { "type": "Transitive", "resolved": "2.1.28", "contentHash": "ha49pzOEDmCPkMxwfPSR/wxa/6RD3r42TESIgpzpi7FXq/gNVUuJVEO+wtUzntNRhtmq3BKCl0s0aAlSZLkBUA==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", "resolved": "8.0.2", "contentHash": "7IQhGK+wjyGrNsPBjJcZwWAr+Wf6D4+TwOptUt77bWtgNkiV8tDEbhFS+dDamtQFZ2X7kWG9m71iZQRj2x3zgQ==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" } }, "Microsoft.Extensions.Configuration.CommandLine": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "NZuZMz3Q8Z780nKX3ifV1fE7lS+6pynDHK71OfU4OZ1ItgvDOhyOC7E6z+JMZrAj63zRpwbdldYFk499t3+1dQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" } }, "Microsoft.Extensions.Configuration.EnvironmentVariables": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "plvZ0ZIpq+97gdPNNvhwvrEZ92kNml9hd1pe3idMA7svR0PztdzVLkoWLcRFgySYXUJc3kSM3Xw3mNFMo/bxRA==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" } }, "Microsoft.Extensions.Configuration.FileExtensions": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "EJzSNO9oaAXnTdtdNO6npPRsIIeZCBSNmdQ091VDO7fBiOtJAAeEq6dtrVXIi3ZyjC5XRSAtVvF8SzcneRHqKQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", "Microsoft.Extensions.FileProviders.Physical": "8.0.0", "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Configuration.Json": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "L89DLNuimOghjV3tLx0ArFDwVEJD6+uGB3BMCMX01kaLzXkaXHb2021xOMl2QOxUxbdePKUZsUY7n2UUkycjRg==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.Configuration.FileExtensions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", "System.Text.Json": "8.0.5" } }, "Microsoft.Extensions.Configuration.UserSecrets": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "7tYqdPPpAK+3jO9d5LTuCK2VxrEdf85Ol4trUr6ds4jclBecadWZ/RyPCbNjfbN5iGTfUnD/h65TOQuqQv2c+A==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.Configuration.Json": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", "Microsoft.Extensions.FileProviders.Physical": "8.0.0" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "8.0.2", "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "doVPCUUCY7c6LhBsEfiy3W1bvS7Mi6LkfQMS8nlC22jZWNxBv8VO8bdfeyvpYFst6Kxqk7HBC6lytmEoBssvSQ==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "elH2vmwNmsXuKmUeMQ4YW9ldXiF+gSGDgg1vORksob5POnpaI6caj1Hu8zaYbEuibhqCoWg0YRWDazBY3zjBfg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Options": "8.0.2", "System.Diagnostics.DiagnosticSource": "8.0.1" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.FileProviders.Physical": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==", "dependencies": { "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", "Microsoft.Extensions.FileSystemGlobbing": "8.0.0", "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.FileSystemGlobbing": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==" }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "nHwq9aPBdBPYXPti6wYEEfgXddfBrYC+CQLn+qISiwQq5tpfaqDZSKOJNxoe9rfQxGf1c+2wC/qWFe1QYJPYqw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.1", "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==", "dependencies": { "Microsoft.Extensions.DependencyInjection": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2", "Microsoft.Extensions.Options": "8.0.2" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "8.0.2", "contentHash": "nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "System.Diagnostics.DiagnosticSource": "8.0.1" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "QWwTrsgOnJMmn+XUslm8D2H1n3PkP/u/v52FODtyBc/k4W9r3i2vcXXeeX/upnzllJYRRbrzVzT0OclfNJtBJA==", "dependencies": { "Microsoft.Extensions.Configuration": "8.0.0", "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.Configuration.Binder": "8.0.2", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Logging": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2", "Microsoft.Extensions.Options": "8.0.2", "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" } }, "Microsoft.Extensions.Logging.Debug": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "B8hqNuYudC2RB+L/DI33uO4rf5by41fZVdcVL2oZj0UyoAZqnwTwYHp1KafoH4nkl1/23piNeybFFASaV2HkFg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Logging": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Logging.EventLog": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "ZD1m4GXoxcZeDJIq8qePKj+QAWeQNO/OG8skvrOG8RQfxLp9MAKRoliTc27xanoNUzeqvX5HhS/I7c0BvwAYUg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Logging": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2", "Microsoft.Extensions.Options": "8.0.2", "System.Diagnostics.EventLog": "8.0.1" } }, "Microsoft.Extensions.Logging.EventSource": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "YMXMAla6B6sEf/SnfZYTty633Ool3AH7KOw2LOaaEqwSo2piK4f7HMtzyc3CNiipDnq1fsUSuG5Oc7ZzpVy8WQ==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Logging": "8.0.1", "Microsoft.Extensions.Logging.Abstractions": "8.0.2", "Microsoft.Extensions.Options": "8.0.2", "Microsoft.Extensions.Primitives": "8.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0", "System.Text.Json": "8.0.5" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "8.0.2", "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", "Microsoft.Extensions.Configuration.Binder": "8.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", "Microsoft.Extensions.Options": "8.0.0", "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "vaoWjvkG1aenR2XdjaVivlCV9fADfgyhW5bZtXT23qaEea0lWiUljdQuze4E31vKM7ZWJaSUsbYIKE3rnzfZUg==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "n1ZP7NM2Gkn/MgD8+eOT5MulMj6wfeQMNS2Pizvq5GHCZfjlFMXV2irQlQmJhwA2VABC57M0auudO89Iu2uRLg==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Text.Json": { "type": "Transitive", "resolved": "8.0.5", "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0", "System.Text.Encodings.Web": "8.0.0" } }, "hangfire.aspnetcore": { "type": "Project", "dependencies": { "Hangfire.NetCore": "[1.0.0, )" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } }, "hangfire.netcore": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[3.0.0, )", "Microsoft.Extensions.Hosting.Abstractions": "[3.0.0, )", "Microsoft.Extensions.Logging.Abstractions": "[3.0.0, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[2.1.28, )", "Hangfire.Core": "[1.0.0, )" } } } } } ================================================ FILE: src/Directory.Build.props ================================================ false true embedded true Latest false CA1200;CA1859;1591 true latest Recommended true true false ================================================ FILE: src/Hangfire.AspNetCore/Dashboard/AspNetCoreDashboardContext.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace Hangfire.Dashboard { public sealed class AspNetCoreDashboardContext : DashboardContext { public AspNetCoreDashboardContext( [NotNull] JobStorage storage, [NotNull] DashboardOptions options, [NotNull] HttpContext httpContext) : base(storage, options) { HttpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext)); Request = new AspNetCoreDashboardRequest(httpContext); Response = new AspNetCoreDashboardResponse(httpContext); if (!options.IgnoreAntiforgeryToken) { var antiforgery = HttpContext.RequestServices.GetService(); var tokenSet = antiforgery?.GetAndStoreTokens(HttpContext); if (tokenSet != null) { AntiforgeryHeader = tokenSet.HeaderName; AntiforgeryToken = tokenSet.RequestToken; } } } public HttpContext HttpContext { get; } public override IBackgroundJobClient GetBackgroundJobClient() { var factory = HttpContext.RequestServices.GetService(); if (factory != null) { return factory.GetClient(Storage); } return HttpContext.RequestServices.GetService() ?? base.GetBackgroundJobClient(); } public override IRecurringJobManager GetRecurringJobManager() { var factory = HttpContext.RequestServices.GetService(); if (factory != null) { return factory.GetManager(Storage); } return HttpContext.RequestServices.GetService() ?? base.GetRecurringJobManager(); } } } ================================================ FILE: src/Hangfire.AspNetCore/Dashboard/AspNetCoreDashboardContextExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Microsoft.AspNetCore.Http; namespace Hangfire.Dashboard { public static class AspNetCoreDashboardContextExtensions { public static HttpContext GetHttpContext([NotNull] this DashboardContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var aspNetCoreContext = context as AspNetCoreDashboardContext; if (aspNetCoreContext == null) { throw new ArgumentException($"Context argument should be of type `{nameof(AspNetCoreDashboardContext)}`!", nameof(context)); } return aspNetCoreContext.HttpContext; } } } ================================================ FILE: src/Hangfire.AspNetCore/Dashboard/AspNetCoreDashboardMiddleware.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Net; using System.Threading.Tasks; using Hangfire.Annotations; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace Hangfire.Dashboard { public class AspNetCoreDashboardMiddleware { private readonly RequestDelegate _next; private readonly JobStorage _storage; private readonly DashboardOptions _options; private readonly RouteCollection _routes; private readonly bool _finalizeWhenNotFound; public AspNetCoreDashboardMiddleware( [NotNull] RequestDelegate next, [NotNull] JobStorage storage, [NotNull] DashboardOptions options, [NotNull] RouteCollection routes) : this(next, storage, options, routes, finalizeWhenNotFound: false) { } public AspNetCoreDashboardMiddleware( [NotNull] RequestDelegate next, [NotNull] JobStorage storage, [NotNull] DashboardOptions options, [NotNull] RouteCollection routes, bool finalizeWhenNotFound) { if (next == null) throw new ArgumentNullException(nameof(next)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (options == null) throw new ArgumentNullException(nameof(options)); if (routes == null) throw new ArgumentNullException(nameof(routes)); _next = next; _storage = storage; _options = options; _routes = routes; _finalizeWhenNotFound = finalizeWhenNotFound; } public async Task Invoke(HttpContext httpContext) { var context = new AspNetCoreDashboardContext(_storage, _options, httpContext); var findResult = _routes.FindDispatcher(httpContext.Request.Path.Value); if (findResult == null) { if (_finalizeWhenNotFound) { // When UsePathBase method is used, such as in MapHangfireDashboard, we should // set 404 status code explicitly to handle non-found endpoints, because no one // will do this for us. // https://github.com/HangfireIO/Hangfire/issues/1729 // https://github.com/HangfireIO/Hangfire/issues/2541 SetResponseStatusCode(httpContext, (int)HttpStatusCode.NotFound); return; } await _next.Invoke(httpContext); return; } // ReSharper disable once LoopCanBeConvertedToQuery foreach (var filter in _options.Authorization) { if (!filter.Authorize(context)) { SetResponseStatusCode(httpContext, GetUnauthorizedStatusCode(httpContext)); return; } } foreach (var filter in _options.AsyncAuthorization) { if (!await filter.AuthorizeAsync(context)) { SetResponseStatusCode(httpContext, GetUnauthorizedStatusCode(httpContext)); return; } } if (!_options.IgnoreAntiforgeryToken) { var antiforgery = httpContext.RequestServices.GetService(); if (antiforgery != null) { var requestValid = await antiforgery.IsRequestValidAsync(httpContext); if (!requestValid) { // Invalid or missing CSRF token SetResponseStatusCode(httpContext, (int) HttpStatusCode.Forbidden); return; } } } context.UriMatch = findResult.Item2; await findResult.Item1.Dispatch(context); } private static void SetResponseStatusCode(HttpContext httpContext, int statusCode) { if (!httpContext.Response.HasStarted) { httpContext.Response.StatusCode = statusCode; } } private static int GetUnauthorizedStatusCode(HttpContext httpContext) { return httpContext.User?.Identity?.IsAuthenticated == true ? (int)HttpStatusCode.Forbidden : (int)HttpStatusCode.Unauthorized; } } } ================================================ FILE: src/Hangfire.AspNetCore/Dashboard/AspNetCoreDashboardRequest.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading.Tasks; using Hangfire.Annotations; using Microsoft.AspNetCore.Http; namespace Hangfire.Dashboard { internal sealed class AspNetCoreDashboardRequest : DashboardRequest { private readonly HttpContext _context; public AspNetCoreDashboardRequest([NotNull] HttpContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); _context = context; } public override string Method => _context.Request.Method; public override string Path => _context.Request.Path.Value; public override string PathBase => _context.Request.PathBase.Value; public override string LocalIpAddress => _context.Connection.LocalIpAddress?.ToString(); public override string RemoteIpAddress => _context.Connection.RemoteIpAddress?.ToString(); public override string GetQuery(string key) => _context.Request.Query[key]; public override async Task> GetFormValuesAsync(string key) { var form = await _context.Request.ReadFormAsync(); return form[key]; } } } ================================================ FILE: src/Hangfire.AspNetCore/Dashboard/AspNetCoreDashboardResponse.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Globalization; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Hangfire.Annotations; namespace Hangfire.Dashboard { internal sealed class AspNetCoreDashboardResponse : DashboardResponse { private readonly HttpContext _context; public AspNetCoreDashboardResponse([NotNull] HttpContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); _context = context; } public override string ContentType { get { return _context.Response.ContentType; } set { if (!_context.Response.HasStarted) { _context.Response.ContentType = value; } } } public override int StatusCode { get { return _context.Response.StatusCode; } set { if (!_context.Response.HasStarted) { _context.Response.StatusCode = value; } } } public override Stream Body => _context.Response.Body; public override Task WriteAsync(string text) { return _context.Response.WriteAsync(text); } public override void SetExpire(DateTimeOffset? value) { if (!_context.Response.HasStarted) { _context.Response.Headers["Expires"] = value?.ToString("r", CultureInfo.InvariantCulture); } } } } ================================================ FILE: src/Hangfire.AspNetCore/Hangfire.AspNetCore.csproj ================================================  net451;net461;netstandard1.3;netstandard2.0;netcoreapp3.0 true $(NoWarn);1591 Hangfire ================================================ FILE: src/Hangfire.AspNetCore/HangfireApplicationBuilderExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Dashboard; using Hangfire.Server; using Microsoft.AspNetCore.Builder; #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER using Microsoft.Extensions.Hosting; #else using Microsoft.AspNetCore.Hosting; #endif using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Hangfire.Common; namespace Hangfire { public static class HangfireApplicationBuilderExtensions { public static IApplicationBuilder UseHangfireDashboard( [NotNull] this IApplicationBuilder app, [NotNull] string pathMatch = "/hangfire", [CanBeNull] DashboardOptions options = null, [CanBeNull] JobStorage storage = null) { if (app == null) throw new ArgumentNullException(nameof(app)); if (pathMatch == null) throw new ArgumentNullException(nameof(pathMatch)); HangfireServiceCollectionExtensions.ThrowIfNotConfigured(app.ApplicationServices); var services = app.ApplicationServices; storage = storage ?? services.GetRequiredService(); options = options ?? services.GetService() ?? new DashboardOptions(); options.TimeZoneResolver = options.TimeZoneResolver ?? services.GetService(); var routes = app.ApplicationServices.GetRequiredService(); app.Map(new PathString(pathMatch), x => x.UseMiddleware(storage, options, routes)); return app; } #if !NET451 && !NETSTANDARD1_3 [Obsolete("Please use IServiceCollection.AddHangfireServer extension method instead in the ConfigureServices method. Will be removed in 2.0.0.")] #endif public static IApplicationBuilder UseHangfireServer( [NotNull] this IApplicationBuilder app, [CanBeNull] BackgroundJobServerOptions options = null, [CanBeNull] IEnumerable additionalProcesses = null, [CanBeNull] JobStorage storage = null) { if (app == null) throw new ArgumentNullException(nameof(app)); HangfireServiceCollectionExtensions.ThrowIfNotConfigured(app.ApplicationServices); var services = app.ApplicationServices; storage = storage ?? services.GetRequiredService(); options = options ?? services.GetService() ?? new BackgroundJobServerOptions(); additionalProcesses = additionalProcesses ?? services.GetServices(); options.Activator = options.Activator ?? services.GetService(); options.FilterProvider = options.FilterProvider ?? services.GetService(); options.TimeZoneResolver = options.TimeZoneResolver ?? services.GetService(); services.RegisterHangfireServer(HangfireServiceCollectionExtensions.GetInternalServices(services, out var factory, out var stateChanger, out var performer) #pragma warning disable 618 ? new BackgroundJobServer(options, storage, additionalProcesses, null, null, factory, performer, stateChanger) #pragma warning restore 618 : new BackgroundJobServer(options, storage, additionalProcesses)); return app; } public static IApplicationBuilder UseHangfireServer( [NotNull] this IApplicationBuilder app, [NotNull] Func serverFactory) { if (app == null) throw new ArgumentNullException(nameof(app)); if (serverFactory == null) throw new ArgumentNullException(nameof(serverFactory)); HangfireServiceCollectionExtensions.ThrowIfNotConfigured(app.ApplicationServices); app.ApplicationServices.RegisterHangfireServer(serverFactory()); return app; } public static IServiceProvider RegisterHangfireServer( [NotNull] this IServiceProvider services, [NotNull] IBackgroundProcessingServer server) { #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER var lifetime = services.GetRequiredService(); #else var lifetime = services.GetRequiredService(); #endif lifetime.ApplicationStopping.Register(server.SendStop); lifetime.ApplicationStopped.Register(server.Dispose); return services; } } } ================================================ FILE: src/Hangfire.AspNetCore/HangfireEndpointRouteBuilderExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Dashboard; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; namespace Hangfire { public static class HangfireEndpointRouteBuilderExtensions { public static IEndpointConventionBuilder MapHangfireDashboard( [NotNull] this IEndpointRouteBuilder endpoints, [CanBeNull] DashboardOptions options = null, [CanBeNull] JobStorage storage = null) { return MapHangfireDashboard(endpoints, "/hangfire", options, storage); } public static IEndpointConventionBuilder MapHangfireDashboard( [NotNull] this IEndpointRouteBuilder endpoints, [NotNull] string pattern, [CanBeNull] DashboardOptions options = null, [CanBeNull] JobStorage storage = null) { if (endpoints == null) throw new ArgumentNullException(nameof(endpoints)); if (pattern == null) throw new ArgumentNullException(nameof(pattern)); var app = endpoints.CreateApplicationBuilder(); HangfireServiceCollectionExtensions.ThrowIfNotConfigured(app.ApplicationServices); var services = app.ApplicationServices; storage = storage ?? services.GetRequiredService(); options = options ?? services.GetService() ?? new DashboardOptions(); options.TimeZoneResolver = options.TimeZoneResolver ?? services.GetService(); var routes = app.ApplicationServices.GetRequiredService(); var pipeline = app .UsePathBase(pattern) .UseMiddleware(storage, options, routes, true) .Build(); return endpoints.Map(pattern + "/{**path}", pipeline); } public static IEndpointConventionBuilder MapHangfireDashboardWithNoAuthorizationFilters( [NotNull] this IEndpointRouteBuilder endpoints, [NotNull] string pattern = "/hangfire", [CanBeNull] DashboardOptions options = null, [CanBeNull] JobStorage storage = null) { if (endpoints == null) throw new ArgumentNullException(nameof(endpoints)); options = options ?? new DashboardOptions(); // We don't require the default LocalRequestsOnlyAuthorizationFilter since we provide our own policy options.Authorization = Enumerable.Empty(); options.AsyncAuthorization = Enumerable.Empty(); return endpoints.MapHangfireDashboard(pattern, options, storage); } public static IEndpointConventionBuilder MapHangfireDashboardWithAuthorizationPolicy( [NotNull] this IEndpointRouteBuilder endpoints, [NotNull] string authorizationPolicyName, [NotNull] string pattern = "/hangfire", [CanBeNull] DashboardOptions options = null, [CanBeNull] JobStorage storage = null) { if (endpoints == null) throw new ArgumentNullException(nameof(endpoints)); if (authorizationPolicyName == null) throw new ArgumentNullException(nameof(authorizationPolicyName)); return endpoints .MapHangfireDashboardWithNoAuthorizationFilters(pattern, options, storage) .RequireAuthorization(authorizationPolicyName); } } } #endif ================================================ FILE: src/Hangfire.AspNetCore/Properties/AssemblyInfo.cs ================================================ using System.Reflection; [assembly: AssemblyTitle("Hangfire.AspNetCore")] [assembly: AssemblyDescription("ASP.NET Core support for Hangfire")] ================================================ FILE: src/Hangfire.AspNetCore/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETCoreApp,Version=v3.0": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "Lge/PbXC53jI1MF2J92X5EZOeKV8Q/rlB1aV3H9I/ZTDyQGOyBcL03IAvnviWpHKj43BDkNy6kU2KKoh8kAS0g==", "dependencies": { "Microsoft.Extensions.Primitives": "3.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "ofQRroDlzJ0xKOtzNuaVt6QKNImFkhkG0lIMpGl7PtXnIf5SuLWBeiQZAP8DNSxDBJJdcsPkiJiMYK2WA5H8dQ==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "kahEeykb6FyQytoZNNXuz74X85B4weIEt8Kd+0klK48bkXDWOIHAOvNjlGsPMcS9CL935Te8QGQS83JqCbpdHA==", "dependencies": { "Microsoft.Extensions.Primitives": "3.0.0" } }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "qeDWS5ErmkUN96BdQqpmeCmLk5HJWQ/SPw3ux5v5/Qb0hKZS5wojBMulnBC7JUEiBwg7Ir71Yjf1lFiRT5MdtQ==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "3.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "3.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "3.0.0", "Microsoft.Extensions.Logging.Abstractions": "3.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "+PsosTYZn+omucI0ff9eywo9QcPLwcbIWf7dz7ZLM1zGR8gVZXJ3wo6+tkuIedUNW5iWENlVJPEvrGjiVeoNNQ==" }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "6gwewTbmOh+ZVBicVkL1XRp79sx4O7BVY6Yy+7OYZdwn3pyOKe9lOam+3gXJ3TZMjhJZdV0Ub8hxHt2vkrmN5Q==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } }, "hangfire.netcore": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[3.0.0, )", "Microsoft.Extensions.Hosting.Abstractions": "[3.0.0, )", "Microsoft.Extensions.Logging.Abstractions": "[3.0.0, )" } } }, ".NETFramework,Version=v4.5.1": { "Microsoft.AspNetCore.Antiforgery": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "oJnrSvL6S7jM2eD/TR/Kyp/7O6pKvN+8FcnYvUaxaHbKlISwl98o44uidzePBjGxTf4fh9NFEx/q3OuuxAvBzw==", "dependencies": { "Microsoft.AspNetCore.DataProtection": "1.0.0", "Microsoft.AspNetCore.Http.Abstractions": "1.0.0", "Microsoft.AspNetCore.WebUtilities": "1.0.0", "Microsoft.Extensions.ObjectPool": "1.0.0" } }, "Microsoft.AspNetCore.Http.Abstractions": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "OJHlqdJOWKKBfsiVdX4Z4KCNuqvBIu6+1MVKuejRDyHnGyMkNHNoP/dtVzhPqvJXaJg9N4HlD0XNc6GDCFVffg==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "1.0.0", "System.Text.Encodings.Web": "4.0.0" } }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net451": "1.0.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.AspNetCore.Cryptography.Internal": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "0btvxwOqYNpKTUQrD7LA3p6Wi0vrhfWGBVqIKPS1KtEdkCv3QoVgFO4eJYuClGDS9NXhqk7TWh46/8x8wtZHaw==" }, "Microsoft.AspNetCore.DataProtection": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "gt4URT+8ljPk0ePspLqOGPJBm+s6iMvsZqweplhf7wiZSjFiG1uYBNpQ/0dFY7wSx3NMRjekyXzCjvkGAV570g==", "dependencies": { "Microsoft.AspNetCore.Cryptography.Internal": "1.0.0", "Microsoft.AspNetCore.DataProtection.Abstractions": "1.0.0", "Microsoft.AspNetCore.Hosting.Abstractions": "1.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0", "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Microsoft.Extensions.Options": "1.0.0" } }, "Microsoft.AspNetCore.DataProtection.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "h5ycDgkqmRdManmYMQVJgzNI7YtVp2X2/os1cKmdfrpfq+m9L8bMKhbd7PCksoLci+aYTOSn45khPl+hpPb9ug==" }, "Microsoft.AspNetCore.Hosting.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "8r6qOl1jYyC523ZKM1QNl+6ijIoYWELWm0tpEWqtTIOg9DytHJWshB7usgqiuRmfHXM0EUziR6ouFY7iP7Tuzw==", "dependencies": { "Microsoft.AspNetCore.Hosting.Server.Abstractions": "1.0.0", "Microsoft.AspNetCore.Http.Abstractions": "1.0.0", "Microsoft.Extensions.Configuration.Abstractions": "1.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "1.0.0", "Microsoft.Extensions.Logging.Abstractions": "1.0.0" } }, "Microsoft.AspNetCore.Hosting.Server.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "sHZyhQEoW15T9E36rfdm5Ux6a6RZB0KNM79ccf2IplWASqmlRGhX4ydU3dzQRLhkHpLx16fnWOL0KScsO6BevQ==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "1.0.0", "Microsoft.Extensions.Configuration.Abstractions": "1.0.0" } }, "Microsoft.AspNetCore.Http.Features": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "6x7zgfbTo1gL9xMEb7EMO2ES/48bqwnWyfH09z+ubWhnzxdhHls8rtqstPylu5FPD9nid6Vo2pgDm5vufRAy5Q==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0" } }, "Microsoft.AspNetCore.WebUtilities": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "D0licSnS1JgqQ/gYlN41wXbeYG3dFIdjY781YzMHZ5gBB7kczacshW+H6plZkXRr/cCnAJWGa31o1R8c5GEy/A==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0", "System.Buffers": "4.0.0", "System.Text.Encodings.Web": "4.0.0" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "nJ+Et/rnDMDmGhxvFAKdN3va7y+YDPICv1nUEP8I4IKgOkWwr/dCZHMqxVhJFrkbW9ux8Kd7erC4mvxfZh0WnA==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0", "System.Linq": "4.1.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "+XwaNo3o9RhLQhUnnOBCaukeRi1X9yYc0Fzye9RlErSflKZdw0VgHtn6rvKo0FTionsW0x8QVULhKH+nkqVjQA==", "dependencies": { "System.ComponentModel": "4.0.1", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "4jsqTxG3py/hYSsOtZMkNJ2/CQqPdpwyK7bDUkrwHgqowCFSmx/C+R4IzQ+2AK2Up1fVcu+ldC0gktwidL828A==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "wHT6oY50q36mAXBRKtFaB7u07WxKC5u2M8fi3PqHOOnHyUo9gD0u1TlCNR8UObHQxKMYwqlgI8TLcErpt29n8A==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "BTXoWSTrv/saLlNSg8l41YOoSKeUUanQLykUqRTtiUJz2xxQOCgm4ckPzrdmSK6w0mdjR2h7IrUDGdBF78Z7yg==" }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "SdP3yPKF++JTkoa91pBDiE70uQkR/gdXWzOnMPbSj+eOqY1vgY+b8RVl+gh7TrJ2wlCK2QqnQtvCQlPPZRK36w==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0", "Microsoft.Extensions.Primitives": "1.0.0", "System.ComponentModel": "4.0.1", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "3q2vzfKEDjL6JFkRpk5SFA3zarYsO6+ZYgoucNImrUMzDn0mFbEOL5p9oPoWiypwypbJVVjWTf557bXZ0YFLig==", "dependencies": { "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0" } }, "Microsoft.NETFramework.ReferenceAssemblies.net451": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "vVPinxdLrwoX81ApbNIHDBI6qymQEy8eSOxDNBgKJtc2+cifnF0oT1U2d3EFx+V5O68yaqna2myZJNsgKCpVkA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "msXumHfjjURSkvxUjYuq4N2ghHoRi2VpXcKMA7gK6ujQfU3vGpl+B6ld0ATRg+FZFpRyA6PgEPA+VlIkTeNf2w==" }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==" }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==" }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "oBZFnm7seFiVfugsIyOvQCWobNZs7FzqDV/B7tx20Ep/l3UUFCPDkdTnCNaJZTU27zjeODmy2C/cP60u3D4c9w==" }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==" }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==" }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==" }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==" }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==" }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==" }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==" }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==" }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==" }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==" }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "TWZnuiJgPDAEEUfobD7njXvSVR2Toz+jvKWds6yL4oSztmKQfnWzucczjzA+6Dv1bktBdY71sZW1YN0X6m9chQ==" }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==" }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } }, "hangfire.netcore": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[1.0.0, )", "Microsoft.Extensions.Logging.Abstractions": "[1.0.0, )" } } }, ".NETFramework,Version=v4.6.1": { "Microsoft.AspNetCore.Antiforgery": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "BFdjKs38tu7UEHhe1eyZ340+oVfusWhtYGGrOKB/JmjAO8nfaF3NrT6oGUVyXGaZzWxTsdJr9BhsEEN/GoQxkQ==", "dependencies": { "Microsoft.AspNetCore.DataProtection": "2.0.0", "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", "Microsoft.AspNetCore.Http.Extensions": "2.0.0", "Microsoft.AspNetCore.WebUtilities": "2.0.0", "Microsoft.Extensions.ObjectPool": "2.0.0" } }, "Microsoft.AspNetCore.Http.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "pblZLY7IfNqhQ5wwGQ0vNq2mG6W5YgZI1fk7suEuwZsGxGEADNBAyNlTALM9L8nMXdvbp6aHP/t4wHrFpcL3Sw==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "2.0.0", "System.Text.Encodings.Web": "4.4.0" } }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.AspNetCore.Cryptography.Internal": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "SY6GQyZZ5o09rqFmy3nhyJzx3lkFDBl0wO2Kb7EoLCPyH6dC7KB+QXysHfa9P5jHPiYB9VEkcQ9H7kQKcXQ1sw==" }, "Microsoft.AspNetCore.DataProtection": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "CjRLA26BpKrzBqpw1g9F3rGYNGisPd+zsnYdpJbHsjH4iIbi/OHfgKzGdHZCwmfQWrlL4e8Q0SpS+DMvgf6Jpg==", "dependencies": { "Microsoft.AspNetCore.Cryptography.Internal": "2.0.0", "Microsoft.AspNetCore.DataProtection.Abstractions": "2.0.0", "Microsoft.AspNetCore.Hosting.Abstractions": "2.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", "Microsoft.Extensions.Options": "2.0.0", "Microsoft.Win32.Registry": "4.4.0", "System.Security.Cryptography.Xml": "4.4.0" } }, "Microsoft.AspNetCore.DataProtection.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "BiFPWLZTKw253oQ5lAXcCkFkNFSRNi8fDCUB2yOTQyuYVMR8pnBAhVJ37o/E6bnuFYrE6eFCU4iDYrShmBIBYA==" }, "Microsoft.AspNetCore.Hosting.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "IR2zlm3d/CmYbkw+cMM7M6mUAi+xsFUPfWqGYqzZVC5o6jX3xD2Z4Uf44UBaWKMBf5Z7q9dodIdXxwFPF2Hxhg==", "dependencies": { "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.0.0", "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", "Microsoft.Extensions.Configuration.Abstractions": "2.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "2.0.0", "Microsoft.Extensions.Hosting.Abstractions": "2.0.0", "Microsoft.Extensions.Logging.Abstractions": "2.0.0" } }, "Microsoft.AspNetCore.Hosting.Server.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "v2H65ix/O11HKoxhKQpljtozsD5/1tqeXr3TYnrLgfAPIsp6kTFxIcTSENoxtew7h9X14ENqUf2lBCkyCNRUuQ==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "2.0.0", "Microsoft.Extensions.Configuration.Abstractions": "2.0.0" } }, "Microsoft.AspNetCore.Http.Extensions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "lA7Bwvur19MhXrlW0w+WBXONJMSFYY5kNazflz4MNwMZMtzwHxNA6fC5sQsssYd/XvA0gMyKwp52s68uuKLR1w==", "dependencies": { "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "2.0.0", "Microsoft.Net.Http.Headers": "2.0.0", "System.Buffers": "4.4.0" } }, "Microsoft.AspNetCore.Http.Features": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "yk62muzFTZTKCQuo3nmVPkPvGBlM2qbdSxbX62TufuONuKQrTGQ/SwhwBbYutk5/YY7u4HETu0n9BKOn7mMgmA==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.AspNetCore.WebUtilities": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "RqDEwy7jdHJ0NunWydSzJrpODnsF7NPdB0KaRdG60H1bMEt4DbjcWkUb+XxjZ15uWCMi7clTQClpPuIFLwD1yQ==", "dependencies": { "Microsoft.Net.Http.Headers": "2.0.0", "System.Text.Encodings.Web": "4.4.0" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "rHFrXqMIvQNq51H8RYTO4IWmDOYh8NUzyqGlh0xHWTP6XYnKk7Ryinys2uDs+Vu88b3AMlM3gBBSs78m6OQpYQ==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "eUdJ0Q/GfVyUJc0Jal5L1QZLceL78pvEM9wEKcHeI24KorqMDoVX+gWsMGLulQMfOwsUaPtkpQM2pFERTzSfSg==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "Z0AK+hmLO33WAXQ5P1uPzhH7z5yjDHX/XnUefXxE//SyvCb9x4cVjND24dT5566t/yzGp8/WLD7EG9KQKZZklQ==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "qPG6Ip/AdHxMJ7j3z8FkkpCbV8yjtiFpf/aOpN3TwfJWbtYpN+BKV8Q+pqPMgk7XZivcju9yARaEVCS++hWopA==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==" }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "drOmgNZCJiNEqFM/TvyqwtogS8wqoWGQCW5KB/CVGKL6VXHw8OOMdaHyspp8HPstP9UDnrnuq+8eaCaAcQg6tA==" }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "sAKBgjl2gWsECBLLR9K54T7/uZaP2n9GhMYHay/oOLfvpvX0+iNAlQ2NJgVE352C9Fs5CDV3VbNTK8T2aNKQFA==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "ukg53qNlqTrK38WA30b5qhw0GD7y3jdI9PHHASjdKyTcBHTevFM2o23tyk3pWCgAV27Bbkm+CPQ2zUe1ZOuYSA==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "4.4.0" } }, "Microsoft.Net.Http.Headers": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "Rm9zeNCWyNrGnysHdRXJpNfeDVlPzzFuidSuRLRNvOrnw71vgNPlR4H9wHo2hG/oSaruukqNjK06MDQqb+eXhA==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0", "System.Buffers": "4.4.0" } }, "Microsoft.NETFramework.ReferenceAssemblies.net461": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "dA36TlNVn/XfrZtmf0fiI/z1nd3Wfp2QVzTdj26pqgP9LFWq0i1hYEUAW50xUjGFYn1+/cP3KGuxT2Yn1OUNBQ==", "dependencies": { "System.Security.AccessControl": "4.4.0", "System.Security.Principal.Windows": "4.4.0" } }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "9dLLuBxr5GNmOfl2jSMcsHuteEg32BEfUotmmUkmZjpR3RpVHE8YQwt0ow3p6prwA1ME8WqDVZqrr8z6H8G+Kw==" }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "2NRFPX/V81ucKQmqNgGBZrKGH/5ejsvivSGMRum0SMgPnJxwhuNkzVS1+7gC3R2X0f57CtwrPrXPPSe6nOp82g==", "dependencies": { "System.Security.Principal.Windows": "4.4.0" } }, "System.Security.Cryptography.Xml": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "1Xubvo4i+K+DO6YzVh6vBKmCl5xx/cAoiJEze6VQ+XwVQU25KQC9pPrmniz2EbbJnmoQ5Rm2FFjHsfQAi0Rs+Q==" }, "System.Security.Principal.Windows": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "pP+AOzt1o3jESOuLmf52YQTF7H3Ng9hTnrOESQiqsnl2IbBh1HInsAMHYtoh75iUYV0OIkHmjvveraYB6zM97w==" }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "l/tYeikqMHX2MD2jzrHDfR9ejrpTTF7wvAEbR51AMvzip1wSJgiURbDik4iv/w7ZgytmTD/hlwpplEhF9bmFNw==" }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } }, "hangfire.netcore": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.0.0, )", "Microsoft.Extensions.Hosting.Abstractions": "[2.0.0, )", "Microsoft.Extensions.Logging.Abstractions": "[2.0.0, )" } } }, ".NETStandard,Version=v1.3": { "Microsoft.AspNetCore.Antiforgery": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "oJnrSvL6S7jM2eD/TR/Kyp/7O6pKvN+8FcnYvUaxaHbKlISwl98o44uidzePBjGxTf4fh9NFEx/q3OuuxAvBzw==", "dependencies": { "Microsoft.AspNetCore.DataProtection": "1.0.0", "Microsoft.AspNetCore.Http.Abstractions": "1.0.0", "Microsoft.AspNetCore.WebUtilities": "1.0.0", "Microsoft.Extensions.ObjectPool": "1.0.0" } }, "Microsoft.AspNetCore.Http.Abstractions": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "OJHlqdJOWKKBfsiVdX4Z4KCNuqvBIu6+1MVKuejRDyHnGyMkNHNoP/dtVzhPqvJXaJg9N4HlD0XNc6GDCFVffg==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "1.0.0", "System.Globalization.Extensions": "4.0.1", "System.Linq.Expressions": "4.1.0", "System.Reflection.TypeExtensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encodings.Web": "4.0.0" } }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "NETStandard.Library": { "type": "Direct", "requested": "[1.6.1, )", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.AspNetCore.Cryptography.Internal": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "0btvxwOqYNpKTUQrD7LA3p6Wi0vrhfWGBVqIKPS1KtEdkCv3QoVgFO4eJYuClGDS9NXhqk7TWh46/8x8wtZHaw==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Threading": "4.0.11" } }, "Microsoft.AspNetCore.DataProtection": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "gt4URT+8ljPk0ePspLqOGPJBm+s6iMvsZqweplhf7wiZSjFiG1uYBNpQ/0dFY7wSx3NMRjekyXzCjvkGAV570g==", "dependencies": { "Microsoft.AspNetCore.Cryptography.Internal": "1.0.0", "Microsoft.AspNetCore.DataProtection.Abstractions": "1.0.0", "Microsoft.AspNetCore.Hosting.Abstractions": "1.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0", "Microsoft.Extensions.Logging.Abstractions": "1.0.0", "Microsoft.Extensions.Options": "1.0.0", "Microsoft.Win32.Registry": "4.0.0", "System.IO.FileSystem": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Security.Claims": "4.0.1", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Security.Principal.Windows": "4.0.0", "System.Xml.XDocument": "4.0.11" } }, "Microsoft.AspNetCore.DataProtection.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "h5ycDgkqmRdManmYMQVJgzNI7YtVp2X2/os1cKmdfrpfq+m9L8bMKhbd7PCksoLci+aYTOSn45khPl+hpPb9ug==", "dependencies": { "System.ComponentModel": "4.0.1", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0" } }, "Microsoft.AspNetCore.Hosting.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "8r6qOl1jYyC523ZKM1QNl+6ijIoYWELWm0tpEWqtTIOg9DytHJWshB7usgqiuRmfHXM0EUziR6ouFY7iP7Tuzw==", "dependencies": { "Microsoft.AspNetCore.Hosting.Server.Abstractions": "1.0.0", "Microsoft.AspNetCore.Http.Abstractions": "1.0.0", "Microsoft.Extensions.Configuration.Abstractions": "1.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "1.0.0", "Microsoft.Extensions.Logging.Abstractions": "1.0.0" } }, "Microsoft.AspNetCore.Hosting.Server.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "sHZyhQEoW15T9E36rfdm5Ux6a6RZB0KNM79ccf2IplWASqmlRGhX4ydU3dzQRLhkHpLx16fnWOL0KScsO6BevQ==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "1.0.0", "Microsoft.Extensions.Configuration.Abstractions": "1.0.0" } }, "Microsoft.AspNetCore.Http.Features": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "6x7zgfbTo1gL9xMEb7EMO2ES/48bqwnWyfH09z+ubWhnzxdhHls8rtqstPylu5FPD9nid6Vo2pgDm5vufRAy5Q==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0", "System.Collections": "4.0.11", "System.ComponentModel": "4.0.1", "System.Linq": "4.1.0", "System.Net.Primitives": "4.0.11", "System.Net.WebSockets": "4.0.0", "System.Runtime.Extensions": "4.1.0", "System.Security.Claims": "4.0.1", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Security.Principal": "4.0.1" } }, "Microsoft.AspNetCore.WebUtilities": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "D0licSnS1JgqQ/gYlN41wXbeYG3dFIdjY781YzMHZ5gBB7kczacshW+H6plZkXRr/cCnAJWGa31o1R8c5GEy/A==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0", "System.Buffers": "4.0.0", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.Text.Encodings.Web": "4.0.0" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "17h8b5mXa87XYKrrVqdgZ38JefSUqLChUQpXgSnpzsM0nDOhE40FTeNWOJ/YmySGV6tG6T8+hjz6vxbknHJr6A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Threading": "4.0.11" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "nJ+Et/rnDMDmGhxvFAKdN3va7y+YDPICv1nUEP8I4IKgOkWwr/dCZHMqxVhJFrkbW9ux8Kd7erC4mvxfZh0WnA==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0", "System.Linq": "4.1.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "+XwaNo3o9RhLQhUnnOBCaukeRi1X9yYc0Fzye9RlErSflKZdw0VgHtn6rvKo0FTionsW0x8QVULhKH+nkqVjQA==", "dependencies": { "System.ComponentModel": "4.0.1", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "4jsqTxG3py/hYSsOtZMkNJ2/CQqPdpwyK7bDUkrwHgqowCFSmx/C+R4IzQ+2AK2Up1fVcu+ldC0gktwidL828A==", "dependencies": { "Microsoft.Extensions.Primitives": "1.0.0", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "wHT6oY50q36mAXBRKtFaB7u07WxKC5u2M8fi3PqHOOnHyUo9gD0u1TlCNR8UObHQxKMYwqlgI8TLcErpt29n8A==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "BTXoWSTrv/saLlNSg8l41YOoSKeUUanQLykUqRTtiUJz2xxQOCgm4ckPzrdmSK6w0mdjR2h7IrUDGdBF78Z7yg==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "SdP3yPKF++JTkoa91pBDiE70uQkR/gdXWzOnMPbSj+eOqY1vgY+b8RVl+gh7TrJ2wlCK2QqnQtvCQlPPZRK36w==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0", "Microsoft.Extensions.Primitives": "1.0.0", "System.ComponentModel": "4.0.1", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "3q2vzfKEDjL6JFkRpk5SFA3zarYsO6+ZYgoucNImrUMzDn0mFbEOL5p9oPoWiypwypbJVVjWTf557bXZ0YFLig==", "dependencies": { "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "q+eLtROUAQ3OxYA5mpQrgyFgzLQxIyrfT2eLpYX5IEPlHmIio2nh4F5bgOaQoGOV865kFKZZso9Oq9RlazvXtg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0" } }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "9.0.1", "contentHash": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", "dependencies": { "Microsoft.CSharp": "4.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Serialization.Primitives": "4.1.1", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11" } }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "oBZFnm7seFiVfugsIyOvQCWobNZs7FzqDV/B7tx20Ep/l3UUFCPDkdTnCNaJZTU27zjeODmy2C/cP60u3D4c9w==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "KKo23iKeOaIg61SSXwjANN7QYDr/3op3OWGGzDzz7mypx0Za0fZSeG0l6cco8Ntp8YMYkIQcAqlk8yhm5/Uhcg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Net.WebSockets": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2KJo8hir6Edi9jnMDAMhiJoI691xRBmKcbNpwjrvpIMOCTYOtBpSsSEGBxBDV7PKbasJNaFp1+PZz1D7xS41Hg==", "dependencies": { "Microsoft.Win32.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Runtime.Serialization.Primitives": { "type": "Transitive", "resolved": "4.1.1", "contentHash": "HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==", "dependencies": { "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Security.Claims": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4Jlp0OgJLS/Voj1kyFP6MJlIYp3crgfH8kNQk2p7+4JYfc1aAmh9PZyAMMbDhuoolGNtux9HqSOazsioRiDvCw==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Security.Principal": "4.0.1" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0" } }, "System.Security.Principal": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "On+SKhXY5rzxh/S8wlH1Rm0ogBlu7zyHNxeNBiXauNrhHRXAe9EuX8Yl5IOzLPGU5Z4kLWHMvORDOCG8iu9hww==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Security.Principal.Windows": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "iFx15AF3RMEPZn3COh8+Bb2Thv2zsmLd93RchS1b8Mj5SNYeGqbYNCSn5AES1+gq56p4ujGZPrl0xN7ngkXOHg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.Win32.Primitives": "4.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Claims": "4.0.1", "System.Security.Principal": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "TWZnuiJgPDAEEUfobD7njXvSVR2Toz+jvKWds6yL4oSztmKQfnWzucczjzA+6Dv1bktBdY71sZW1YN0X6m9chQ==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Threading.ThreadPool": { "type": "Transitive", "resolved": "4.0.10", "contentHash": "IMXgB5Vf/5Qw1kpoVgJMOvUO1l32aC+qC3OaIZjWJOjvcxuxNWOK2ZTWWYXfij22NHxT2j1yWX5vlAeQWld9vA==", "dependencies": { "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "NETStandard.Library": "[1.6.1, )", "Newtonsoft.Json": "[9.0.1, )", "System.Threading.Thread": "[4.0.0, )", "System.Threading.ThreadPool": "[4.0.10, )" } }, "hangfire.netcore": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[1.0.0, )", "Microsoft.Extensions.Logging.Abstractions": "[1.0.0, )", "NETStandard.Library": "[1.6.1, )" } } }, ".NETStandard,Version=v2.0": { "Microsoft.AspNetCore.Antiforgery": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "BFdjKs38tu7UEHhe1eyZ340+oVfusWhtYGGrOKB/JmjAO8nfaF3NrT6oGUVyXGaZzWxTsdJr9BhsEEN/GoQxkQ==", "dependencies": { "Microsoft.AspNetCore.DataProtection": "2.0.0", "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", "Microsoft.AspNetCore.Http.Extensions": "2.0.0", "Microsoft.AspNetCore.WebUtilities": "2.0.0", "Microsoft.Extensions.ObjectPool": "2.0.0" } }, "Microsoft.AspNetCore.Http.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "pblZLY7IfNqhQ5wwGQ0vNq2mG6W5YgZI1fk7suEuwZsGxGEADNBAyNlTALM9L8nMXdvbp6aHP/t4wHrFpcL3Sw==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "2.0.0", "System.Text.Encodings.Web": "4.4.0" } }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.AspNetCore.Cryptography.Internal": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "SY6GQyZZ5o09rqFmy3nhyJzx3lkFDBl0wO2Kb7EoLCPyH6dC7KB+QXysHfa9P5jHPiYB9VEkcQ9H7kQKcXQ1sw==" }, "Microsoft.AspNetCore.DataProtection": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "CjRLA26BpKrzBqpw1g9F3rGYNGisPd+zsnYdpJbHsjH4iIbi/OHfgKzGdHZCwmfQWrlL4e8Q0SpS+DMvgf6Jpg==", "dependencies": { "Microsoft.AspNetCore.Cryptography.Internal": "2.0.0", "Microsoft.AspNetCore.DataProtection.Abstractions": "2.0.0", "Microsoft.AspNetCore.Hosting.Abstractions": "2.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", "Microsoft.Extensions.Options": "2.0.0", "Microsoft.Win32.Registry": "4.4.0", "System.Security.Cryptography.Xml": "4.4.0" } }, "Microsoft.AspNetCore.DataProtection.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "BiFPWLZTKw253oQ5lAXcCkFkNFSRNi8fDCUB2yOTQyuYVMR8pnBAhVJ37o/E6bnuFYrE6eFCU4iDYrShmBIBYA==" }, "Microsoft.AspNetCore.Hosting.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "IR2zlm3d/CmYbkw+cMM7M6mUAi+xsFUPfWqGYqzZVC5o6jX3xD2Z4Uf44UBaWKMBf5Z7q9dodIdXxwFPF2Hxhg==", "dependencies": { "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.0.0", "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", "Microsoft.Extensions.Configuration.Abstractions": "2.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "2.0.0", "Microsoft.Extensions.Hosting.Abstractions": "2.0.0", "Microsoft.Extensions.Logging.Abstractions": "2.0.0" } }, "Microsoft.AspNetCore.Hosting.Server.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "v2H65ix/O11HKoxhKQpljtozsD5/1tqeXr3TYnrLgfAPIsp6kTFxIcTSENoxtew7h9X14ENqUf2lBCkyCNRUuQ==", "dependencies": { "Microsoft.AspNetCore.Http.Features": "2.0.0", "Microsoft.Extensions.Configuration.Abstractions": "2.0.0" } }, "Microsoft.AspNetCore.Http.Extensions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "lA7Bwvur19MhXrlW0w+WBXONJMSFYY5kNazflz4MNwMZMtzwHxNA6fC5sQsssYd/XvA0gMyKwp52s68uuKLR1w==", "dependencies": { "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "2.0.0", "Microsoft.Net.Http.Headers": "2.0.0", "System.Buffers": "4.4.0" } }, "Microsoft.AspNetCore.Http.Features": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "yk62muzFTZTKCQuo3nmVPkPvGBlM2qbdSxbX62TufuONuKQrTGQ/SwhwBbYutk5/YY7u4HETu0n9BKOn7mMgmA==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.AspNetCore.WebUtilities": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "RqDEwy7jdHJ0NunWydSzJrpODnsF7NPdB0KaRdG60H1bMEt4DbjcWkUb+XxjZ15uWCMi7clTQClpPuIFLwD1yQ==", "dependencies": { "Microsoft.Net.Http.Headers": "2.0.0", "System.Text.Encodings.Web": "4.4.0" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "rHFrXqMIvQNq51H8RYTO4IWmDOYh8NUzyqGlh0xHWTP6XYnKk7Ryinys2uDs+Vu88b3AMlM3gBBSs78m6OQpYQ==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "eUdJ0Q/GfVyUJc0Jal5L1QZLceL78pvEM9wEKcHeI24KorqMDoVX+gWsMGLulQMfOwsUaPtkpQM2pFERTzSfSg==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "Z0AK+hmLO33WAXQ5P1uPzhH7z5yjDHX/XnUefXxE//SyvCb9x4cVjND24dT5566t/yzGp8/WLD7EG9KQKZZklQ==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "qPG6Ip/AdHxMJ7j3z8FkkpCbV8yjtiFpf/aOpN3TwfJWbtYpN+BKV8Q+pqPMgk7XZivcju9yARaEVCS++hWopA==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==" }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "drOmgNZCJiNEqFM/TvyqwtogS8wqoWGQCW5KB/CVGKL6VXHw8OOMdaHyspp8HPstP9UDnrnuq+8eaCaAcQg6tA==" }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "sAKBgjl2gWsECBLLR9K54T7/uZaP2n9GhMYHay/oOLfvpvX0+iNAlQ2NJgVE352C9Fs5CDV3VbNTK8T2aNKQFA==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", "Microsoft.Extensions.Primitives": "2.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "ukg53qNlqTrK38WA30b5qhw0GD7y3jdI9PHHASjdKyTcBHTevFM2o23tyk3pWCgAV27Bbkm+CPQ2zUe1ZOuYSA==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "4.4.0" } }, "Microsoft.Net.Http.Headers": { "type": "Transitive", "resolved": "2.0.0", "contentHash": "Rm9zeNCWyNrGnysHdRXJpNfeDVlPzzFuidSuRLRNvOrnw71vgNPlR4H9wHo2hG/oSaruukqNjK06MDQqb+eXhA==", "dependencies": { "Microsoft.Extensions.Primitives": "2.0.0", "System.Buffers": "4.4.0" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "dA36TlNVn/XfrZtmf0fiI/z1nd3Wfp2QVzTdj26pqgP9LFWq0i1hYEUAW50xUjGFYn1+/cP3KGuxT2Yn1OUNBQ==", "dependencies": { "System.Security.AccessControl": "4.4.0", "System.Security.Principal.Windows": "4.4.0" } }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "9dLLuBxr5GNmOfl2jSMcsHuteEg32BEfUotmmUkmZjpR3RpVHE8YQwt0ow3p6prwA1ME8WqDVZqrr8z6H8G+Kw==" }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "2NRFPX/V81ucKQmqNgGBZrKGH/5ejsvivSGMRum0SMgPnJxwhuNkzVS1+7gC3R2X0f57CtwrPrXPPSe6nOp82g==", "dependencies": { "System.Security.Principal.Windows": "4.4.0" } }, "System.Security.Cryptography.Xml": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "1Xubvo4i+K+DO6YzVh6vBKmCl5xx/cAoiJEze6VQ+XwVQU25KQC9pPrmniz2EbbJnmoQ5Rm2FFjHsfQAi0Rs+Q==" }, "System.Security.Principal.Windows": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "pP+AOzt1o3jESOuLmf52YQTF7H3Ng9hTnrOESQiqsnl2IbBh1HInsAMHYtoh75iUYV0OIkHmjvveraYB6zM97w==" }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "l/tYeikqMHX2MD2jzrHDfR9ejrpTTF7wvAEbR51AMvzip1wSJgiURbDik4iv/w7ZgytmTD/hlwpplEhF9bmFNw==" }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } }, "hangfire.netcore": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Microsoft.Extensions.DependencyInjection.Abstractions": "[2.0.0, )", "Microsoft.Extensions.Hosting.Abstractions": "[2.0.0, )", "Microsoft.Extensions.Logging.Abstractions": "[2.0.0, )" } } } } } ================================================ FILE: src/Hangfire.Core/AppBuilderExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Dashboard; using Hangfire.Dashboard.Owin; using Hangfire.Logging; using Hangfire.Server; using Owin; using Microsoft.Owin; using Microsoft.Owin.Infrastructure; namespace Hangfire { using BuildFunc = Action< Func< IDictionary, Func< Func, Task>, Func, Task> >>>; /// /// Provides extension methods for the IAppBuilder interface /// defined in the Owin /// NuGet package to simplify the integration with OWIN applications. /// /// /// /// /// This class simplifies Hangfire configuration in OWIN applications, /// please read /// Getting Started with OWIN and Katana if you aren't familiar with OWIN /// and/or don't know what is the Startup class. /// /// /// The methods of this class should be called from OWIN's Startup /// class. /// ///

UseHangfireDashboard

/// Dashboard UI contains pages that allow you to monitor almost every /// aspect of background processing. It is exposed as an OWIN middleware that /// intercepts requests to the given path. /// OWIN implementation of Dashboard UI allows to use it outside of web /// applications, including console applications and Windows Services. /// /// By default, an access to the Dashboard UI is restricted only to local /// requests for security reasons. Before publishing a project to /// production, make sure you still have access to the Dashboard UI by using the /// /// Hangfire.Dashboard.Authorization package. /// ///

UseHangfireServer

/// In addition to creation of a new instance of the /// class, these methods also register the call to its /// method on application shutdown. This is done via registering a callback on the corresponding /// from OWIN environment ("host.OnAppDisposing" or /// "server.OnDispose" keys). /// This enables graceful shutdown feature for background jobs and background processes /// without any additional configuration. /// Please see for more details regarding /// background processing. ///
/// /// ///

Basic Configuration

/// Basic setup in an OWIN application looks like the following example. Please note /// that job storage should be configured before using the methods of this class. /// /// /// ///

Adding Dashboard Only

/// If you want to install dashboard without starting a background job server, for example, /// to process background jobs outside of your web application, call only the /// . /// /// /// ///

Change Dashboard Path

/// By default, you can access Dashboard UI by hitting the http(s)://<app>/hangfire /// URL, however you can change it as in the following example. /// /// /// ///

Configuring Authorization

/// The following example demonstrates how to change default local-requests-only /// authorization for Dashboard UI. /// /// /// ///

Changing Application Path

/// Have you seen the Back to site button in the Dashboard? By default it leads /// you to the root of your site, but you can configure the behavior. /// /// /// ///

Multiple Dashboards

/// The following example demonstrates adding multiple Dashboard UI endpoints. This may /// be useful when you are using multiple shards for your background processing needs. /// /// /// ///
/// /// /// /// /// Hangfire.Dashboard.Authorization Package /// /// /// [EditorBrowsable(EditorBrowsableState.Never)] public static class AppBuilderExtensions { // Prevent GC to collect background processing servers in hosts that do // not support shutdown notifications. Dictionary is used as a Set. private static readonly ConcurrentDictionary Servers = new ConcurrentDictionary(); /// /// Creates a new instance of the class /// with default options and storage and /// registers its disposal on application shutdown. /// /// OWIN application builder. /// /// is null. /// /// OWIN environment does not contain the application shutdown cancellation token. /// /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireServer([NotNull] this IAppBuilder builder) { return builder.UseHangfireServer(new BackgroundJobServerOptions()); } /// /// Creates a new instance of the class /// with the given collection of additional background processes and /// storage, and registers its disposal /// on application shutdown. /// /// OWIN application builder. /// Collection of additional background processes. /// /// is null. /// is null. /// /// OWIN environment does not contain the application shutdown cancellation token. /// /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireServer( [NotNull] this IAppBuilder builder, [NotNull] params IBackgroundProcess[] additionalProcesses) { return builder.UseHangfireServer(JobStorage.Current, new BackgroundJobServerOptions(), additionalProcesses); } /// /// Creates a new instance of the class /// with the specified options and storage, /// and registers its disposal on application shutdown. /// /// OWIN application builder. /// Options for background job server. /// /// is null. /// is null. /// /// OWIN environment does not contain the application shutdown cancellation token. /// /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireServer( [NotNull] this IAppBuilder builder, [NotNull] BackgroundJobServerOptions options) { return builder.UseHangfireServer(options, JobStorage.Current); } /// /// Creates a new instance of the class /// with the specified options, given collection of background processes /// and storage, and registers its /// disposal on application shutdown. /// /// OWIN application builder. /// Options for background job server. /// Collection of additional background processes. /// /// is null. /// is null. /// is null. /// /// OWIN environment does not contain the application shutdown cancellation token. /// /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireServer( [NotNull] this IAppBuilder builder, [NotNull] BackgroundJobServerOptions options, [NotNull] params IBackgroundProcess[] additionalProcesses) { return builder.UseHangfireServer(JobStorage.Current, options, additionalProcesses); } /// /// Creates a new instance of the class /// with the given options and specified storage, and registers its disposal /// on application shutdown. /// /// OWIN application builder. /// Options for background job server. /// Storage to use by background job server. /// /// is null. /// is null. /// is null. /// /// OWIN environment does not contain the application shutdown cancellation token. /// /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireServer( [NotNull] this IAppBuilder builder, [NotNull] BackgroundJobServerOptions options, [NotNull] JobStorage storage) { return builder.UseHangfireServer(storage, options); } /// /// Starts a new instance of the class with /// the given arguments, and registers its disposal on application shutdown. /// /// /// OWIN application builder. /// Storage to use by background job server. /// Options for background job server. /// Collection of additional background processes. /// /// is null. /// is null. /// is null. /// is null. /// /// OWIN environment does not contain the application shutdown cancellation token. /// /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireServer( [NotNull] this IAppBuilder builder, [NotNull] JobStorage storage, [NotNull] BackgroundJobServerOptions options, [NotNull] params IBackgroundProcess[] additionalProcesses) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (options == null) throw new ArgumentNullException(nameof(options)); if (additionalProcesses == null) throw new ArgumentNullException(nameof(additionalProcesses)); return UseHangfireServer(builder, new BackgroundJobServer(options, storage, additionalProcesses)); } /// /// Registers the given custom instance of the /// interface for disposal on application shutdown. /// /// /// OWIN application builder. /// Custom background processing server instance. /// /// is null. /// is null. /// /// OWIN environment does not contain the application shutdown cancellation token. /// /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireServer( [NotNull] this IAppBuilder builder, [NotNull] IBackgroundProcessingServer server) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (server == null) throw new ArgumentNullException(nameof(server)); Servers.TryAdd(server, null); var context = new OwinContext(builder.Properties); var token = context.Get("host.OnAppDisposing"); if (token == default(CancellationToken)) { // https://github.com/owin/owin/issues/27 token = context.Get("server.OnDispose"); } if (token == default(CancellationToken)) { throw new InvalidOperationException( "Current OWIN environment does not contain an instance of the `CancellationToken` class neither under `host.OnAppDisposing`, nor `server.OnDispose` key.\r\n" + "Please use another OWIN host or create an instance of the `BackgroundJobServer` class manually."); } token.Register(OnAppDisposing, server); return builder; } private static void OnAppDisposing(object state) { var logger = LogProvider.GetLogger(typeof(AppBuilderExtensions)); logger.Info("Web application is shutting down via OWIN's host.OnAppDisposing callback."); ((IDisposable) state).Dispose(); if (state is IBackgroundProcessingServer server) Servers.TryRemove(server, out _); } /// /// Adds Dashboard UI middleware to the OWIN request processing pipeline under /// the /hangfire path, for the storage. /// /// OWIN application builder. /// /// is null. /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireDashboard([NotNull] this IAppBuilder builder) { return builder.UseHangfireDashboard("/hangfire"); } /// /// Adds Dashboard UI middleware to the OWIN request processing pipeline under /// the given path, for the storage. /// /// OWIN application builder. /// Path prefix for middleware to use, e.g. "/hangfire". /// /// is null. /// is null. /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireDashboard( [NotNull] this IAppBuilder builder, [NotNull] string pathMatch) { return builder.UseHangfireDashboard(pathMatch, new DashboardOptions()); } /// /// Adds Dashboard UI middleware to the OWIN request processing pipeline under /// the specified path and the given options, for the /// storage. /// /// OWIN application builder. /// Path prefix for middleware to use, e.g. "/hangfire". /// Options for Dashboard UI. /// /// is null. /// is null. /// is null. /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireDashboard( [NotNull] this IAppBuilder builder, [NotNull] string pathMatch, [NotNull] DashboardOptions options) { return builder.UseHangfireDashboard(pathMatch, options, JobStorage.Current); } /// /// Adds Dashboard UI middleware to the OWIN request processing pipeline with the /// specified parameters. /// /// OWIN application builder. /// Path prefix for middleware to use, e.g. "/hangfire". /// Options for Dashboard UI. /// Job storage to use by Dashboard IO. /// /// is null. /// is null. /// is null. /// is null. /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireDashboard( [NotNull] this IAppBuilder builder, [NotNull] string pathMatch, [NotNull] DashboardOptions options, [NotNull] JobStorage storage) { return builder.UseHangfireDashboard(pathMatch, options, storage, null); } /// /// Adds Dashboard UI middleware to the OWIN request processing pipeline with the /// specified parameters and antiforgery service. /// /// OWIN application builder. /// Path prefix for middleware to use, e.g. "/hangfire". /// Options for Dashboard UI. /// Job storage to use by Dashboard IO. /// Antiforgery service. /// /// is null. /// is null. /// is null. /// is null. /// /// /// Please see for details and examples. /// public static IAppBuilder UseHangfireDashboard( [NotNull] this IAppBuilder builder, [NotNull] string pathMatch, [NotNull] DashboardOptions options, [NotNull] JobStorage storage, [CanBeNull] IOwinDashboardAntiforgery antiforgery) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (pathMatch == null) throw new ArgumentNullException(nameof(pathMatch)); if (options == null) throw new ArgumentNullException(nameof(options)); if (storage == null) throw new ArgumentNullException(nameof(storage)); SignatureConversions.AddConversions(builder); builder.Map(pathMatch, subApp => subApp .UseOwin() .UseHangfireDashboard(options, storage, DashboardRoutes.Routes, antiforgery)); return builder; } private static BuildFunc UseOwin(this IAppBuilder builder) { return middleware => builder.Use(middleware(builder.Properties)); } } } ================================================ FILE: src/Hangfire.Core/App_Packages/LibLog.1.4/LibLog.cs ================================================ //=============================================================================== // LibLog // // https://github.com/damianh/LibLog //=============================================================================== // Copyright © 2011-2014 Damian Hickey. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //=============================================================================== using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading; using Hangfire.Logging.LogProviders; // ReSharper disable All namespace Hangfire.Logging { using System.Collections.Generic; using System; using System.Diagnostics; using System.Globalization; /// /// Simple interface that represent a logger. /// public interface ILog { /// /// Log a message the specified log level. /// /// The log level. /// The message function. /// An optional exception. /// true if the message was logged. Otherwise false. /// /// Note to implementers: the message func should not be called if the loglevel is not enabled /// so as not to incur performance penalties. /// /// To check IsEnabled call Log with only LogLevel and check the return value, no event will be written /// bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null); } /// /// The log level. /// public enum LogLevel { Trace, Debug, Info, Warn, Error, Fatal } public static class LogExtensions { public static bool IsDebugEnabled(this ILog logger) { GuardAgainstNullLogger(logger); return logger.Log(LogLevel.Debug, null); } public static bool IsErrorEnabled(this ILog logger) { GuardAgainstNullLogger(logger); return logger.Log(LogLevel.Error, null); } public static bool IsFatalEnabled(this ILog logger) { GuardAgainstNullLogger(logger); return logger.Log(LogLevel.Fatal, null); } public static bool IsInfoEnabled(this ILog logger) { GuardAgainstNullLogger(logger); return logger.Log(LogLevel.Info, null); } public static bool IsTraceEnabled(this ILog logger) { GuardAgainstNullLogger(logger); return logger.Log(LogLevel.Trace, null); } public static bool IsWarnEnabled(this ILog logger) { GuardAgainstNullLogger(logger); return logger.Log(LogLevel.Warn, null); } public static void Debug(this ILog logger, Func messageFunc) { GuardAgainstNullLogger(logger); logger.Log(LogLevel.Debug, messageFunc); } public static void Debug(this ILog logger, string message) { if (logger.IsDebugEnabled()) { logger.Log(LogLevel.Debug, message.AsFunc()); } } public static void DebugFormat(this ILog logger, string message, params object[] args) { if (logger.IsDebugEnabled()) { logger.LogFormat(LogLevel.Debug, message, args); } } public static void DebugException(this ILog logger, string message, Exception exception) { if (logger.IsDebugEnabled()) { logger.Log(LogLevel.Debug, message.AsFunc(), exception); } } public static void Error(this ILog logger, Func messageFunc) { logger.Log(LogLevel.Error, messageFunc); } public static void Error(this ILog logger, string message) { if (logger.IsErrorEnabled()) { logger.Log(LogLevel.Error, message.AsFunc()); } } public static void ErrorFormat(this ILog logger, string message, params object[] args) { if (logger.IsErrorEnabled()) { logger.LogFormat(LogLevel.Error, message, args); } } public static void ErrorException(this ILog logger, string message, Exception exception) { if (logger.IsErrorEnabled()) { logger.Log(LogLevel.Error, message.AsFunc(), exception); } } public static void Fatal(this ILog logger, Func messageFunc) { logger.Log(LogLevel.Fatal, messageFunc); } public static void Fatal(this ILog logger, string message) { if (logger.IsFatalEnabled()) { logger.Log(LogLevel.Fatal, message.AsFunc()); } } public static void FatalFormat(this ILog logger, string message, params object[] args) { if (logger.IsFatalEnabled()) { logger.LogFormat(LogLevel.Fatal, message, args); } } public static void FatalException(this ILog logger, string message, Exception exception) { if (logger.IsFatalEnabled()) { logger.Log(LogLevel.Fatal, message.AsFunc(), exception); } } public static void Info(this ILog logger, Func messageFunc) { GuardAgainstNullLogger(logger); logger.Log(LogLevel.Info, messageFunc); } public static void Info(this ILog logger, string message) { if (logger.IsInfoEnabled()) { logger.Log(LogLevel.Info, message.AsFunc()); } } public static void InfoFormat(this ILog logger, string message, params object[] args) { if (logger.IsInfoEnabled()) { logger.LogFormat(LogLevel.Info, message, args); } } public static void InfoException(this ILog logger, string message, Exception exception) { if (logger.IsInfoEnabled()) { logger.Log(LogLevel.Info, message.AsFunc(), exception); } } public static void Trace(this ILog logger, Func messageFunc) { GuardAgainstNullLogger(logger); logger.Log(LogLevel.Trace, messageFunc); } public static void Trace(this ILog logger, string message) { if (logger.IsTraceEnabled()) { logger.Log(LogLevel.Trace, message.AsFunc()); } } public static void TraceFormat(this ILog logger, string message, params object[] args) { if (logger.IsTraceEnabled()) { logger.LogFormat(LogLevel.Trace, message, args); } } public static void TraceException(this ILog logger, string message, Exception exception) { if (logger.IsTraceEnabled()) { logger.Log(LogLevel.Trace, message.AsFunc(), exception); } } public static void Warn(this ILog logger, Func messageFunc) { GuardAgainstNullLogger(logger); logger.Log(LogLevel.Warn, messageFunc); } public static void Warn(this ILog logger, string message) { if (logger.IsWarnEnabled()) { logger.Log(LogLevel.Warn, message.AsFunc()); } } public static void WarnFormat(this ILog logger, string message, params object[] args) { if (logger.IsWarnEnabled()) { logger.LogFormat(LogLevel.Warn, message, args); } } public static void WarnException(this ILog logger, string message, Exception exception) { if (logger.IsWarnEnabled()) { logger.Log(LogLevel.Warn, message.AsFunc(), exception); } } private static void GuardAgainstNullLogger(ILog logger) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } } private static void LogFormat(this ILog logger, LogLevel logLevel, string message, params object[] args) { var result = string.Format(CultureInfo.InvariantCulture, message, args); logger.Log(logLevel, result.AsFunc()); } // Avoid the closure allocation, see https://gist.github.com/AArnott/d285feef75c18f6ecd2b private static Func AsFunc(this T value) where T : class { return value.Return; } private static T Return(this T value) { return value; } } /// /// Represents a way to get a /// public interface ILogProvider { ILog GetLogger(string name); } /// /// Provides a mechanism to create instances of objects. /// public static class LogProvider { private static ILogProvider _currentLogProvider; /// /// Gets a logger for the specified type. /// /// The type whose name will be used for the logger. /// An instance of public static ILog For() { return GetLogger(typeof(T)); } #if !NETSTANDARD1_3 /// /// Gets a logger for the current class. /// /// An instance of public static ILog GetCurrentClassLogger() { var stackFrame = new StackFrame(1, false); return GetLogger(stackFrame.GetMethod().DeclaringType); } #endif /// /// Gets a logger for the specified type. /// /// The type whose name will be used for the logger. /// An instance of public static ILog GetLogger(Type type) { return GetLogger(type.FullName); } /// /// Gets a logger with the specified name. /// /// The name. /// An instance of public static ILog GetLogger(string name) { ILogProvider logProvider = Volatile.Read(ref _currentLogProvider) ?? ResolveLogProvider(); return logProvider == null ? new NoOpLogger() : (ILog)new LoggerExecutionWrapper(logProvider.GetLogger(name)); } /// /// Sets the current log provider. /// /// The log provider. public static void SetCurrentLogProvider(ILogProvider logProvider) { Volatile.Write(ref _currentLogProvider, logProvider); } internal delegate bool IsLoggerAvailable(); internal delegate ILogProvider CreateLogProvider(); internal static readonly List> LogProviderResolvers = new List> { new Tuple(SerilogLogProvider.IsLoggerAvailable, static () => new SerilogLogProvider()), new Tuple(NLogLogProvider.IsLoggerAvailable, static () => new NLogLogProvider()), new Tuple(Log4NetLogProvider.IsLoggerAvailable, static () => new Log4NetLogProvider()), #if !NETSTANDARD1_3 new Tuple(EntLibLogProvider.IsLoggerAvailable, static () => new EntLibLogProvider()), new Tuple(LoupeLogProvider.IsLoggerAvailable, static () => new LoupeLogProvider()), new Tuple(ElmahLogProvider.IsLoggerAvailable, static () => new ElmahLogProvider()), #endif }; private static ILogProvider ResolveLogProvider() { try { foreach (var providerResolver in LogProviderResolvers) { if (providerResolver.Item1()) { return providerResolver.Item2(); } } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { Console.WriteLine( "Exception occured resolving a log provider. Logging for this assembly {0} is disabled. {1}", typeof(LogProvider).GetTypeInfo().Assembly.FullName, ex); } return null; } internal sealed class NoOpLogProvider : ILogProvider { public static readonly NoOpLogProvider Instance = new NoOpLogProvider(); public ILog GetLogger(string name) { return NoOpLogger.Instance; } } internal sealed class NoOpLogger : ILog { public static readonly NoOpLogger Instance = new NoOpLogger(); public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { return false; } } } internal sealed class LoggerExecutionWrapper : ILog { private readonly ILog _logger; public const string FailedToGenerateLogMessage = "Failed to generate log message"; public ILog WrappedLogger { get { return _logger; } } public LoggerExecutionWrapper(ILog logger) { _logger = logger; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null) { if (messageFunc == null) { return _logger.Log(logLevel, null); } Func wrappedMessageFunc = () => { try { return messageFunc(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { Log(LogLevel.Error, static () => FailedToGenerateLogMessage, ex); } return null; }; return _logger.Log(logLevel, wrappedMessageFunc, exception); } } } namespace Hangfire.Logging.LogProviders { using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq.Expressions; using System.Reflection; using System.Text; public class NLogLogProvider : ILogProvider { private readonly Func _getLoggerByNameDelegate; private static bool _providerIsAvailableOverride = true; public NLogLogProvider() { if (!IsLoggerAvailable()) { throw new InvalidOperationException("NLog.LogManager not found"); } _getLoggerByNameDelegate = GetGetLoggerMethodCall(); } public static bool ProviderIsAvailableOverride { get { return _providerIsAvailableOverride; } set { _providerIsAvailableOverride = value; } } public ILog GetLogger(string name) { return new NLogLogger(_getLoggerByNameDelegate(name)); } public static bool IsLoggerAvailable() { return ProviderIsAvailableOverride && GetLogManagerType() != null; } private static Type GetLogManagerType() { return Type.GetType("NLog.LogManager, NLog"); } private static Func GetGetLoggerMethodCall() { Type logManagerType = GetLogManagerType(); MethodInfo method = logManagerType.GetRuntimeMethod("GetLogger", new[] { typeof(string) }); ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] { nameParam }); return Expression.Lambda>(methodCall, new[] { nameParam }).Compile(); } internal sealed class NLogLogger : ILog { private readonly dynamic _logger; internal NLogLogger(dynamic logger) { _logger = logger; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { if (messageFunc == null) { return IsLogLevelEnable(logLevel); } if (exception != null) { return LogException(logLevel, messageFunc, exception); } switch (logLevel) { case LogLevel.Debug: if (_logger.IsDebugEnabled) { _logger.Debug(messageFunc()); return true; } break; case LogLevel.Info: if (_logger.IsInfoEnabled) { _logger.Info(messageFunc()); return true; } break; case LogLevel.Warn: if (_logger.IsWarnEnabled) { _logger.Warn(messageFunc()); return true; } break; case LogLevel.Error: if (_logger.IsErrorEnabled) { _logger.Error(messageFunc()); return true; } break; case LogLevel.Fatal: if (_logger.IsFatalEnabled) { _logger.Fatal(messageFunc()); return true; } break; default: if (_logger.IsTraceEnabled) { _logger.Trace(messageFunc()); return true; } break; } return false; } private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) { switch (logLevel) { case LogLevel.Debug: if (_logger.IsDebugEnabled) { _logger.DebugException(messageFunc(), exception); return true; } break; case LogLevel.Info: if (_logger.IsInfoEnabled) { _logger.InfoException(messageFunc(), exception); return true; } break; case LogLevel.Warn: if (_logger.IsWarnEnabled) { _logger.WarnException(messageFunc(), exception); return true; } break; case LogLevel.Error: if (_logger.IsErrorEnabled) { _logger.ErrorException(messageFunc(), exception); return true; } break; case LogLevel.Fatal: if (_logger.IsFatalEnabled) { _logger.FatalException(messageFunc(), exception); return true; } break; default: if (_logger.IsTraceEnabled) { _logger.TraceException(messageFunc(), exception); return true; } break; } return false; } private bool IsLogLevelEnable(LogLevel logLevel) { switch (logLevel) { case LogLevel.Debug: return _logger.IsDebugEnabled; case LogLevel.Info: return _logger.IsInfoEnabled; case LogLevel.Warn: return _logger.IsWarnEnabled; case LogLevel.Error: return _logger.IsErrorEnabled; case LogLevel.Fatal: return _logger.IsFatalEnabled; default: return _logger.IsTraceEnabled; } } } } public class Log4NetLogProvider : ILogProvider { private readonly Func _getLoggerByNameDelegate; private static bool _providerIsAvailableOverride = true; public Log4NetLogProvider() { if (!IsLoggerAvailable()) { throw new InvalidOperationException("log4net.LogManager not found"); } _getLoggerByNameDelegate = GetGetLoggerMethodCall(); } public static bool ProviderIsAvailableOverride { get { return _providerIsAvailableOverride; } set { _providerIsAvailableOverride = value; } } public ILog GetLogger(string name) { return new Log4NetLogger(_getLoggerByNameDelegate(name)); } public static bool IsLoggerAvailable() { return ProviderIsAvailableOverride && GetLogManagerType() != null; } private static Type GetLogManagerType() { return Type.GetType("log4net.LogManager, log4net"); } private static Func GetGetLoggerMethodCall() { Type logManagerType = GetLogManagerType(); MethodInfo method = logManagerType.GetRuntimeMethod("GetLogger", new[] { typeof(Assembly), typeof(string) }); ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] { Expression.Constant(typeof(Log4NetLogProvider).GetTypeInfo().Assembly), nameParam }); return Expression.Lambda>(methodCall, new[] { nameParam }).Compile(); } internal sealed class Log4NetLogger : ILog { private readonly dynamic _logger; internal Log4NetLogger(dynamic logger) { _logger = logger; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { if (messageFunc == null) { return IsLogLevelEnable(logLevel); } if (exception != null) { return LogException(logLevel, messageFunc, exception); } switch (logLevel) { case LogLevel.Info: if (_logger.IsInfoEnabled) { _logger.Info(messageFunc()); return true; } break; case LogLevel.Warn: if (_logger.IsWarnEnabled) { _logger.Warn(messageFunc()); return true; } break; case LogLevel.Error: if (_logger.IsErrorEnabled) { _logger.Error(messageFunc()); return true; } break; case LogLevel.Fatal: if (_logger.IsFatalEnabled) { _logger.Fatal(messageFunc()); return true; } break; default: if (_logger.IsDebugEnabled) { _logger.Debug(messageFunc()); // Log4Net doesn't have a 'Trace' level, so all Trace messages are written as 'Debug' return true; } break; } return false; } private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) { switch (logLevel) { case LogLevel.Info: if (_logger.IsDebugEnabled) { _logger.Info(messageFunc(), exception); return true; } break; case LogLevel.Warn: if (_logger.IsWarnEnabled) { _logger.Warn(messageFunc(), exception); return true; } break; case LogLevel.Error: if (_logger.IsErrorEnabled) { _logger.Error(messageFunc(), exception); return true; } break; case LogLevel.Fatal: if (_logger.IsFatalEnabled) { _logger.Fatal(messageFunc(), exception); return true; } break; default: if (_logger.IsDebugEnabled) { _logger.Debug(messageFunc(), exception); return true; } break; } return false; } private bool IsLogLevelEnable(LogLevel logLevel) { switch (logLevel) { case LogLevel.Debug: return _logger.IsDebugEnabled; case LogLevel.Info: return _logger.IsInfoEnabled; case LogLevel.Warn: return _logger.IsWarnEnabled; case LogLevel.Error: return _logger.IsErrorEnabled; case LogLevel.Fatal: return _logger.IsFatalEnabled; default: return _logger.IsDebugEnabled; } } } } #if !NETSTANDARD1_3 public class EntLibLogProvider : ILogProvider { private const string TypeTemplate = "Microsoft.Practices.EnterpriseLibrary.Logging.{0}, Microsoft.Practices.EnterpriseLibrary.Logging"; private static bool _providerIsAvailableOverride = true; private static readonly Type LogEntryType; private static readonly Type LoggerType; private readonly Action WriteLogEntry; private Func ShouldLogEntry; static EntLibLogProvider() { LogEntryType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "LogEntry")); LoggerType = Type.GetType(string.Format(CultureInfo.InvariantCulture, TypeTemplate, "Logger")); } public EntLibLogProvider() { if (!IsLoggerAvailable()) { throw new InvalidOperationException("Microsoft.Practices.EnterpriseLibrary.Logging.Logger not found"); } WriteLogEntry = GetWriteLogEntry(); ShouldLogEntry = GetShouldLogEntry(); } public static bool ProviderIsAvailableOverride { get { return _providerIsAvailableOverride; } set { _providerIsAvailableOverride = value; } } public ILog GetLogger(string name) { return new EntLibLogger(name, WriteLogEntry, ShouldLogEntry); } public static bool IsLoggerAvailable() { return ProviderIsAvailableOverride && LogEntryType != null; } private static Action GetWriteLogEntry() { // new LogEntry(...) var logNameParameter = Expression.Parameter(typeof(string), "logName"); var messageParameter = Expression.Parameter(typeof(string), "message"); var severityParameter = Expression.Parameter(typeof(TraceEventType), "severity"); MemberInitExpression memberInit = GetWriteLogExpression(messageParameter, severityParameter, logNameParameter); //Logger.Write(new LogEntry(....)); MethodInfo writeLogEntryMethod = LoggerType.GetMethod("Write", new[] { LogEntryType }); var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); return Expression.Lambda>( writeLogEntryExpression, logNameParameter, messageParameter, severityParameter).Compile(); } private static Func GetShouldLogEntry() { // new LogEntry(...) var logNameParameter = Expression.Parameter(typeof(string), "logName"); var severityParameter = Expression.Parameter(typeof(TraceEventType), "severity"); MemberInitExpression memberInit = GetWriteLogExpression(Expression.Constant("***dummy***"), severityParameter, logNameParameter); //Logger.Write(new LogEntry(....)); MethodInfo writeLogEntryMethod = LoggerType.GetMethod("ShouldLog", new[] { LogEntryType }); var writeLogEntryExpression = Expression.Call(writeLogEntryMethod, memberInit); return Expression.Lambda>( writeLogEntryExpression, logNameParameter, severityParameter).Compile(); } private static MemberInitExpression GetWriteLogExpression(Expression message, ParameterExpression severityParameter, ParameterExpression logNameParameter) { var entryType = LogEntryType; MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), new MemberBinding[] { Expression.Bind(entryType.GetProperty("Message"), message), Expression.Bind(entryType.GetProperty("Severity"), severityParameter), Expression.Bind(entryType.GetProperty("TimeStamp"), Expression.Property(null, typeof (DateTime).GetProperty("UtcNow"))), Expression.Bind(entryType.GetProperty("Categories"), Expression.ListInit( Expression.New(typeof (List)), typeof (List).GetMethod("Add", new[] {typeof (string)}), logNameParameter)) }); return memberInit; } internal sealed class EntLibLogger : ILog { private readonly string _loggerName; private readonly Action _writeLog; private readonly Func _shouldLog; internal EntLibLogger(string loggerName, Action writeLog, Func shouldLog) { _loggerName = loggerName; _writeLog = writeLog; _shouldLog = shouldLog; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { var severity = MapSeverity(logLevel); if (messageFunc == null) { return _shouldLog(_loggerName, severity); } if (exception != null) { return LogException(logLevel, messageFunc, exception); } _writeLog(_loggerName, messageFunc(), severity); return true; } public bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) { var severity = MapSeverity(logLevel); var message = messageFunc() + Environment.NewLine + exception; _writeLog(_loggerName, message, severity); return true; } private static TraceEventType MapSeverity(LogLevel logLevel) { switch (logLevel) { case LogLevel.Fatal: return TraceEventType.Critical; case LogLevel.Error: return TraceEventType.Error; case LogLevel.Warn: return TraceEventType.Warning; case LogLevel.Info: return TraceEventType.Information; default: return TraceEventType.Verbose; } } } } #endif public class SerilogLogProvider : ILogProvider { private readonly Func _getLoggerByNameDelegate; private readonly SerilogCallbacks _callbacks; private static bool _providerIsAvailableOverride = true; public SerilogLogProvider() { if (!IsLoggerAvailable()) { throw new InvalidOperationException("Serilog.Log not found"); } _getLoggerByNameDelegate = GetForContextMethodCall(); _callbacks = new SerilogCallbacks(); } public static bool ProviderIsAvailableOverride { get { return _providerIsAvailableOverride; } set { _providerIsAvailableOverride = value; } } public ILog GetLogger(string name) { return new SerilogLogger(_callbacks, _getLoggerByNameDelegate(name)); } public static bool IsLoggerAvailable() { return ProviderIsAvailableOverride && GetLogManagerType() != null; } private static Type GetLogManagerType() { return Type.GetType("Serilog.Log, Serilog"); } private static Func GetForContextMethodCall() { Type logManagerType = GetLogManagerType(); MethodInfo method = logManagerType.GetRuntimeMethod("ForContext", new[] { typeof(string), typeof(object), typeof(bool) }); ParameterExpression propertyNameParam = Expression.Parameter(typeof(string), "propertyName"); ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); ParameterExpression destructureObjectsParam = Expression.Parameter(typeof(bool), "destructureObjects"); MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] { propertyNameParam, valueParam, destructureObjectsParam }); var func = Expression.Lambda>(methodCall, new[] { propertyNameParam, valueParam, destructureObjectsParam }).Compile(); return name => func("SourceContext", name, false); } internal sealed class SerilogCallbacks { private static object[] EmptyArray = []; public readonly object DebugLevel; public readonly object ErrorLevel; public readonly object FatalLevel; public readonly object InformationLevel; public readonly object VerboseLevel; public readonly object WarningLevel; public readonly Func IsEnabled; public readonly Action Write; public readonly Action WriteException; public SerilogCallbacks() { var logEventTypeType = Type.GetType("Serilog.Events.LogEventLevel, Serilog"); DebugLevel = Enum.Parse(logEventTypeType, "Debug"); ErrorLevel = Enum.Parse(logEventTypeType, "Error"); FatalLevel = Enum.Parse(logEventTypeType, "Fatal"); InformationLevel = Enum.Parse(logEventTypeType, "Information"); VerboseLevel = Enum.Parse(logEventTypeType, "Verbose"); WarningLevel = Enum.Parse(logEventTypeType, "Warning"); // Func isEnabled = (logger, level) => { return ((SeriLog.ILogger)logger).IsEnabled(level); } var loggerType = Type.GetType("Serilog.ILogger, Serilog"); MethodInfo isEnabledMethodInfo = loggerType.GetRuntimeMethod("IsEnabled", new Type[] { logEventTypeType }); ParameterExpression instanceParam = Expression.Parameter(typeof(object)); UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); ParameterExpression levelParam = Expression.Parameter(typeof(object)); UnaryExpression levelCast = Expression.Convert(levelParam, logEventTypeType); MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); IsEnabled = Expression.Lambda>(isEnabledMethodCall, new[] { instanceParam, levelParam }).Compile(); // Action Write = // (logger, level, message) => { ((SeriLog.ILoggerILogger)logger).Write(level, message, new object[]); } MethodInfo writeMethodInfo = loggerType.GetRuntimeMethod("Write", new[] { logEventTypeType, typeof(string), typeof(object[]) }); ParameterExpression messageParam = Expression.Parameter(typeof(string)); ConstantExpression propertyValuesParam = Expression.Constant(EmptyArray); MethodCallExpression writeMethodExp = Expression.Call(instanceCast, writeMethodInfo, levelCast, messageParam, propertyValuesParam); Write = Expression.Lambda>(writeMethodExp, new[] { instanceParam, levelParam, messageParam }).Compile(); // Action WriteException = // (logger, level, exception, message) => { ((ILogger)logger).Write(level, exception, message, new object[]); } MethodInfo writeExceptionMethodInfo = loggerType.GetRuntimeMethod("Write", new[] { logEventTypeType, typeof(Exception), typeof(string), typeof(object[]) }); ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); writeMethodExp = Expression.Call( instanceCast, writeExceptionMethodInfo, levelCast, exceptionParam, messageParam, propertyValuesParam); WriteException = Expression.Lambda>(writeMethodExp, new[] { instanceParam, levelParam, exceptionParam, messageParam, }).Compile(); } } internal sealed class SerilogLogger : ILog { private readonly SerilogCallbacks _callbacks; private readonly object _logger; internal SerilogLogger(SerilogCallbacks callbacks, object logger) { _callbacks = callbacks; _logger = logger; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { if (messageFunc == null) { return _callbacks.IsEnabled(_logger, logLevel); } if (exception != null) { return LogException(logLevel, messageFunc, exception); } switch (logLevel) { case LogLevel.Debug: if (_callbacks.IsEnabled(_logger, _callbacks.DebugLevel)) { _callbacks.Write(_logger, _callbacks.DebugLevel, messageFunc()); return true; } break; case LogLevel.Info: if (_callbacks.IsEnabled(_logger, _callbacks.InformationLevel)) { _callbacks.Write(_logger, _callbacks.InformationLevel, messageFunc()); return true; } break; case LogLevel.Warn: if (_callbacks.IsEnabled(_logger, _callbacks.WarningLevel)) { _callbacks.Write(_logger, _callbacks.WarningLevel, messageFunc()); return true; } break; case LogLevel.Error: if (_callbacks.IsEnabled(_logger, _callbacks.ErrorLevel)) { _callbacks.Write(_logger, _callbacks.ErrorLevel, messageFunc()); return true; } break; case LogLevel.Fatal: if (_callbacks.IsEnabled(_logger, _callbacks.FatalLevel)) { _callbacks.Write(_logger, _callbacks.FatalLevel, messageFunc()); return true; } break; default: if (_callbacks.IsEnabled(_logger, _callbacks.VerboseLevel)) { _callbacks.Write(_logger, _callbacks.VerboseLevel, messageFunc()); return true; } break; } return false; } private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) { switch (logLevel) { case LogLevel.Debug: if (_callbacks.IsEnabled(_logger, _callbacks.DebugLevel)) { _callbacks.WriteException(_logger, _callbacks.DebugLevel, exception, messageFunc()); return true; } break; case LogLevel.Info: if (_callbacks.IsEnabled(_logger, _callbacks.InformationLevel)) { _callbacks.WriteException(_logger, _callbacks.InformationLevel, exception, messageFunc()); return true; } break; case LogLevel.Warn: if (_callbacks.IsEnabled(_logger, _callbacks.WarningLevel)) { _callbacks.WriteException(_logger, _callbacks.WarningLevel, exception, messageFunc()); return true; } break; case LogLevel.Error: if (_callbacks.IsEnabled(_logger, _callbacks.ErrorLevel)) { _callbacks.WriteException(_logger, _callbacks.ErrorLevel, exception, messageFunc()); return true; } break; case LogLevel.Fatal: if (_callbacks.IsEnabled(_logger, _callbacks.FatalLevel)) { _callbacks.WriteException(_logger, _callbacks.FatalLevel, exception, messageFunc()); return true; } break; default: if (_callbacks.IsEnabled(_logger, _callbacks.VerboseLevel)) { _callbacks.WriteException(_logger, _callbacks.VerboseLevel, exception, messageFunc()); return true; } break; } return false; } } } #if !NETSTANDARD1_3 public class LoupeLogProvider : ILogProvider { private static bool _providerIsAvailableOverride = true; private readonly WriteDelegate _logWriteDelegate; public LoupeLogProvider() { if (!IsLoggerAvailable()) { throw new InvalidOperationException("Gibraltar.Agent.Log (Loupe) not found"); } _logWriteDelegate = GetLogWriteDelegate(); } /// /// Gets or sets a value indicating whether [provider is available override]. Used in tests. /// /// /// true if [provider is available override]; otherwise, false. /// public static bool ProviderIsAvailableOverride { get { return _providerIsAvailableOverride; } set { _providerIsAvailableOverride = value; } } public ILog GetLogger(string name) { return new LoupeLogger(name, _logWriteDelegate); } public static bool IsLoggerAvailable() { return ProviderIsAvailableOverride && GetLogManagerType() != null; } private static Type GetLogManagerType() { return Type.GetType("Gibraltar.Agent.Log, Gibraltar.Agent"); } private static WriteDelegate GetLogWriteDelegate() { Type logManagerType = GetLogManagerType(); Type logMessageSeverityType = Type.GetType("Gibraltar.Agent.LogMessageSeverity, Gibraltar.Agent"); Type logWriteModeType = Type.GetType("Gibraltar.Agent.LogWriteMode, Gibraltar.Agent"); MethodInfo method = logManagerType.GetMethod("Write", new[] { logMessageSeverityType, typeof(string), typeof(int), typeof(Exception), typeof(bool), logWriteModeType, typeof(string), typeof(string), typeof(string), typeof(string), typeof(object[]) }); return (WriteDelegate) method.CreateDelegate(typeof (WriteDelegate)); } internal sealed class LoupeLogger : ILog { private const string LogSystem = "LibLog"; private readonly string _category; private readonly WriteDelegate _logWriteDelegate; private readonly int _skipLevel; internal LoupeLogger(string category, WriteDelegate logWriteDelegate) { _category = category; _logWriteDelegate = logWriteDelegate; _skipLevel = 1; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { if (messageFunc == null) { //nothing to log.. return true; } _logWriteDelegate((int)LoupeLogger.ToLogMessageSeverity(logLevel), LogSystem, _skipLevel, exception, true, 0, null, _category, null, messageFunc.Invoke()); return true; } private static TraceEventType ToLogMessageSeverity(LogLevel logLevel) { switch (logLevel) { case LogLevel.Trace: return TraceEventType.Verbose; case LogLevel.Debug: return TraceEventType.Verbose; case LogLevel.Info: return TraceEventType.Information; case LogLevel.Warn: return TraceEventType.Warning; case LogLevel.Error: return TraceEventType.Error; case LogLevel.Fatal: return TraceEventType.Critical; default: throw new ArgumentOutOfRangeException(nameof(logLevel)); } } } /// /// The form of the Loupe Log.Write method we're using /// internal delegate void WriteDelegate( int severity, string logSystem, int skipFrames, Exception exception, bool attributeToException, int writeMode, string detailsXml, string category, string caption, string description, params object[] args ); } #endif public class ColouredConsoleLogProvider : ILogProvider { private readonly LogLevel _minLevel; static ColouredConsoleLogProvider() { MessageFormatter = DefaultMessageFormatter; Colors = new Dictionary { { LogLevel.Fatal, ConsoleColor.Red }, { LogLevel.Error, ConsoleColor.Yellow }, { LogLevel.Warn, ConsoleColor.Magenta }, { LogLevel.Info, ConsoleColor.White }, { LogLevel.Debug, ConsoleColor.Gray }, { LogLevel.Trace, ConsoleColor.DarkGray }, }; } public ColouredConsoleLogProvider() : this(LogLevel.Info) { } public ColouredConsoleLogProvider(LogLevel minLevel) { _minLevel = minLevel; } public ILog GetLogger(string name) { return new ColouredConsoleLogger(name, _minLevel); } /// /// A delegate returning a formatted log message /// /// The name of the Logger /// The Log Level /// The Log Message /// The Exception, if there is one /// A formatted Log Message string. [SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Public API, can not change in minor versions.")] public delegate string MessageFormatterDelegate( string loggerName, LogLevel level, object message, Exception e); public static Dictionary Colors { get; set; } public static MessageFormatterDelegate MessageFormatter { get; set; } protected static string DefaultMessageFormatter(string loggerName, LogLevel level, object message, Exception e) { var stringBuilder = new StringBuilder(); stringBuilder.Append(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss", CultureInfo.InvariantCulture)); stringBuilder.Append(' '); // Append a readable representation of the log level #pragma warning disable CA1311 stringBuilder.Append(("[" + level.ToString().ToUpper( #if !NETSTANDARD1_3 CultureInfo.InvariantCulture #endif ) + "]").PadRight(8)); #pragma warning restore CA1311 stringBuilder.Append("(" + loggerName + ") "); // Append the message stringBuilder.Append(message); // Append stack trace if there is an exception if (e != null) { stringBuilder.Append(Environment.NewLine).Append(e); } return stringBuilder.ToString(); } internal sealed class ColouredConsoleLogger : ILog { private static readonly object Lock = new object(); private readonly string _name; private readonly LogLevel _minLevel; public ColouredConsoleLogger(string name, LogLevel minLevel) { _name = name; _minLevel = minLevel; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { if (logLevel < _minLevel) { return false; } if (messageFunc == null) { return true; } Write(logLevel, messageFunc(), exception); return true; } private void Write(LogLevel logLevel, string message, Exception e = null) { var formattedMessage = MessageFormatter(_name, logLevel, message, e); ConsoleColor color; if (Colors.TryGetValue(logLevel, out color)) { lock (Lock) { var originalColor = Console.ForegroundColor; try { Console.ForegroundColor = color; Console.Out.WriteLine(formattedMessage); } finally { Console.ForegroundColor = originalColor; } } } else { Console.Out.WriteLine(formattedMessage); } } } } #if !NETSTANDARD1_3 public class ElmahLogProvider : ILogProvider { private static bool _providerIsAvailableOverride = true; private const LogLevel DefaultMinLevel = LogLevel.Error; private readonly Type _errorType; private readonly LogLevel _minLevel; private readonly Func _getErrorLogDelegate; public ElmahLogProvider() : this(DefaultMinLevel) { } public ElmahLogProvider(LogLevel minLevel) { if (!IsLoggerAvailable()) { throw new InvalidOperationException("`Elmah.ErrorLog` or `Elmah.Error` type not found"); } _minLevel = minLevel; _errorType = GetErrorType(); _getErrorLogDelegate = GetGetErrorLogMethodCall(); } public static bool ProviderIsAvailableOverride { get { return _providerIsAvailableOverride; } set { _providerIsAvailableOverride = value; } } public ILog GetLogger(string name) { return new ElmahLog(_minLevel, _getErrorLogDelegate(), _errorType); } public static bool IsLoggerAvailable() { return ProviderIsAvailableOverride && GetLogManagerType() != null && GetErrorType() != null; } private static Type GetLogManagerType() { return Type.GetType("Elmah.ErrorLog, Elmah"); } private static Type GetHttpContextType() { return Type.GetType( $"System.Web.HttpContext, System.Web, Version={Environment.Version}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); } private static Type GetErrorType() { return Type.GetType("Elmah.Error, Elmah"); } private static Func GetGetErrorLogMethodCall() { Type logManagerType = GetLogManagerType(); Type httpContextType = GetHttpContextType(); MethodInfo method = logManagerType.GetMethod("GetDefault", new[] { httpContextType }); ConstantExpression contextValue = Expression.Constant(null, httpContextType); MethodCallExpression methodCall = Expression.Call(null, method, new Expression[] { contextValue }); return Expression.Lambda>(methodCall).Compile(); } internal sealed class ElmahLog : ILog { private readonly LogLevel _minLevel; private readonly Type _errorType; private readonly dynamic _errorLog; public ElmahLog(LogLevel minLevel, dynamic errorLog, Type errorType) { _minLevel = minLevel; _errorType = errorType; _errorLog = errorLog; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception) { if (messageFunc == null) return logLevel >= _minLevel; var message = messageFunc(); dynamic error = exception == null ? Activator.CreateInstance(_errorType) : Activator.CreateInstance(_errorType, exception); error.Message = message; error.Type = logLevel.ToString(); error.Time = DateTime.Now; error.ApplicationName = "Hangfire"; try { _errorLog.Log(error); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { Debug.Print("Error: {0}\n{1}", ex.Message, ex.StackTrace); } return true; } } } #endif } ================================================ FILE: src/Hangfire.Core/App_Packages/StackTraceFormatter/StackTraceFormatter.cs ================================================ #region Copyright (c) 2011 Atif Aziz. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #endregion // ReSharper disable All namespace Hangfire { using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using MoreLinq; partial class StackTraceHtmlFragments : IStackTraceFormatter { public string BeforeType { get; set; } public string AfterType { get; set; } public string BeforeMethod { get; set; } public string AfterMethod { get; set; } public string BeforeParameterType { get; set; } public string AfterParameterType { get; set; } public string BeforeParameterName { get; set; } public string AfterParameterName { get; set; } public string BeforeFile { get; set; } public string AfterFile { get; set; } public string BeforeLine { get; set; } public string AfterLine { get; set; } public string BeforeFrame { get; set; } public string AfterFrame { get; set; } public string BeforeParameters { get; set; } public string AfterParameters { get; set; } string IStackTraceFormatter.Text(string text) => string.IsNullOrEmpty(text) ? string.Empty : WebUtility.HtmlEncode(text); string IStackTraceFormatter.Type(string markup) => BeforeType + markup + AfterType; string IStackTraceFormatter.Method(string markup) => BeforeMethod + markup + AfterMethod; string IStackTraceFormatter.ParameterType(string markup) => BeforeParameterType + markup + AfterParameterType; string IStackTraceFormatter.ParameterName(string markup) => BeforeParameterName + markup + AfterParameterName; string IStackTraceFormatter.File(string markup) => BeforeFile + markup + AfterFile; string IStackTraceFormatter.Line(string markup) => BeforeLine + markup + AfterLine; string IStackTraceFormatter.BeforeFrame => BeforeFrame ?? string.Empty; string IStackTraceFormatter.AfterFrame => AfterFrame ?? string.Empty; string IStackTraceFormatter.BeforeParameters => BeforeParameters ?? string.Empty; string IStackTraceFormatter.AfterParameters => AfterParameters ?? string.Empty; } partial interface IStackTraceFormatter { T Text (string text); T Type (T markup); T Method (T markup); T ParameterType (T markup); T ParameterName (T markup); T File (T markup); T Line (T markup); T BeforeFrame { get; } T AfterFrame { get; } T BeforeParameters { get; } T AfterParameters { get; } } static partial class StackTraceFormatter { static readonly StackTraceHtmlFragments DefaultStackTraceHtmlFragments = new StackTraceHtmlFragments(); public static string FormatHtml(string text, IStackTraceFormatter formatter) { return string.Concat(Format(text, formatter ?? DefaultStackTraceHtmlFragments)); } public static IEnumerable Format(string text, IStackTraceFormatter formatter) { Debug.Assert(text != null); var frames = StackTraceParser.Parse ( text, (idx, len, txt) => new { Index = idx, End = idx + len, Text = txt, Markup = formatter.Text(txt), }, (t, m) => new { Type = new { t.Index, t.End, Markup = formatter.Type(t.Markup) }, Method = new { m.Index, m.End, Markup = formatter.Method(m.Markup) } }, (t, n) => new { Type = new { t.Index, t.End, Markup = formatter.ParameterType(t.Markup) }, Name = new { n.Index, n.End, Markup = formatter.ParameterName(n.Markup) } }, (p, ps) => new { List = p, Parameters = ps.ToArray() }, (f, l) => new { File = f.Text.Length > 0 ? new { f.Index, f.End, Markup = formatter.File(f.Markup) } : null, Line = l.Text.Length > 0 ? new { l.Index, l.End, Markup = formatter.Line(l.Markup) } : null, }, (f, tm, p, fl) => from tokens in new[] { new[] { new { f.Index, End = f.Index, Markup = formatter.BeforeFrame }, tm.Type, tm.Method, new { p.List.Index, End = p.List.Index, Markup = formatter.BeforeParameters }, }, from pe in p.Parameters from e in new[] { pe.Type, pe.Name } select e, new[] { new { Index = p.List.End, p.List.End, Markup = formatter.AfterParameters }, fl.File, fl.Line, new { Index = f.End, f.End, Markup = formatter.AfterFrame }, }, } from token in tokens where token != null select token ); return from token in Enumerable.Repeat(new { Index = 0, End = 0, Markup = default(T) }, 1) .Concat(from tokens in frames from token in tokens select token) .Pairwise((prev, curr) => new { Previous = prev, Current = curr }) from m in new[] { formatter.Text(text.Substring(token.Previous.End, token.Current.Index - token.Previous.End)), token.Current.Markup, } select m; } } } ================================================ FILE: src/Hangfire.Core/App_Packages/StackTraceParser/StackTraceParser.cs ================================================ #region Copyright (c) 2011 Atif Aziz. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #endregion // ReSharper disable once CheckNamespace namespace Hangfire { #region Imports using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; #endregion // ReSharper disable once PartialTypeWithSinglePart partial class StackTraceParser { const string Space = @"[\x20\t]"; const string NotSpace = @"[^\x20\t]"; static readonly Regex Regex = new Regex(@" ^ " + Space + @"* \w+ " + Space + @"+ (? (? " + NotSpace + @"+ ) \. (? " + NotSpace + @"+? ) " + Space + @"* (? \( ( " + Space + @"* \) | (? .+?) " + Space + @"+ (? .+?) (, " + Space + @"* (? .+?) " + Space + @"+ (? .+?) )* \) ) ) ( " + Space + @"+ ( # Microsoft .NET stack traces \w+ " + Space + @"+ (? ( [a-z] \: # Windows rooted path starting with a drive letter | / ) # *nix rooted path starting with a forward-slash .+? ) \: \w+ " + Space + @"+ (? [0-9]+ ) \p{P}? | # Mono stack traces \[0x[0-9a-f]+\] " + Space + @"+ \w+ " + Space + @"+ <(? [^>]+ )> :(? [0-9]+ ) ) )? ) \s* $", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled, // Cap the evaluation time to make it obvious should the expression // fall into the "catastrophic backtracking" trap due to over // generalization. // https://github.com/atifaziz/StackTraceParser/issues/4 TimeSpan.FromMilliseconds(100)); public static IEnumerable Parse( string text, Func>, string, string, T> selector) { if (selector == null) throw new ArgumentNullException(nameof(selector)); return Parse(text, (idx, len, txt) => txt, (t, m) => new { Type = t, Method = m }, (pt, pn) => new KeyValuePair(pt, pn), // ReSharper disable once PossibleMultipleEnumeration (pl, ps) => new { List = pl, Items = ps }, (fn, ln) => new { File = fn, Line = ln }, (f, tm, p, fl) => selector(f, tm.Type, tm.Method, p.List, p.Items, fl.File, fl.Line)); } public static IEnumerable Parse( string text, Func tokenSelector, Func methodSelector, Func parameterSelector, Func, TParameters> parametersSelector, Func sourceLocationSelector, Func selector) { if (tokenSelector == null) throw new ArgumentNullException(nameof(tokenSelector)); if (methodSelector == null) throw new ArgumentNullException(nameof(methodSelector)); if (parameterSelector == null) throw new ArgumentNullException(nameof(parameterSelector)); if (parametersSelector == null) throw new ArgumentNullException(nameof(parametersSelector)); if (sourceLocationSelector == null) throw new ArgumentNullException(nameof(sourceLocationSelector)); if (selector == null) throw new ArgumentNullException(nameof(selector)); return from Match m in Regex.Matches(text) select m.Groups into groups let pt = groups["pt"].Captures let pn = groups["pn"].Captures select selector(Token(groups["frame"], tokenSelector), methodSelector( Token(groups["type"], tokenSelector), Token(groups["method"], tokenSelector)), parametersSelector( Token(groups["params"], tokenSelector), from i in Enumerable.Range(0, pt.Count) select parameterSelector(Token(pt[i], tokenSelector), Token(pn[i], tokenSelector))), sourceLocationSelector(Token(groups["file"], tokenSelector), Token(groups["line"], tokenSelector))); } static T Token(Capture capture, Func tokenSelector) { return tokenSelector(capture.Index, capture.Length, capture.Value); } } } ================================================ FILE: src/Hangfire.Core/AttemptsExceededAction.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire { /// /// Specifies a candidate state for a background job that will be chosen /// by the filter after exceeding /// the number of retry attempts. /// public enum AttemptsExceededAction { /// /// Background job will be moved to the . /// Fail = 0, /// /// Background job will be moved to the . /// Delete } } ================================================ FILE: src/Hangfire.Core/AutomaticRetryAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.ComponentModel; using System.Linq; using System.Reflection; using Hangfire.Common; using Hangfire.Logging; using Hangfire.States; using Hangfire.Storage; using Newtonsoft.Json; namespace Hangfire { /// /// Represents a job filter that performs automatic retries for /// background jobs whose processing was failed due to an exception, with /// a limited number of attempts. /// /// /// /// Filter is added to the global /// collection by default. Intervals between attempts are based on increasing /// exponential back-off multiplier in seconds. /// /// This filter works in a state election phase by changing the /// candidate state from to the /// when another retry should be attempted, or other state based on the value /// of the property when attempts exceeded. /// /// /// /// ///

Disabling Automatic Retries

/// The following example shows how to disable automatic retries for /// a specific job method by applying an attribute to a method. /// /// Even if you disable filter, /// your background jobs can still be executed several times, due to re-queue /// on shutdown and other compensation logic that guarantees the at least /// once processing. /// /// /// ///

Overriding Defaults

/// The following example shows how to override the default number of /// retry attempts for all of the background jobs by modifying the global /// collection. /// /// /// ///

Specifying Attempts Exceeded Action

/// The following example shows how to ignore a background job when /// number of retry attempts exceed using the /// property. /// /// Choose action /// when you aren't interested in processing background job that failed several /// times. /// /// ///
/// /// public sealed class AutomaticRetryAttribute : JobFilterAttribute, IElectStateFilter, IApplyStateFilter { /// /// Represents the default number of retry attempts. This field is read-only. /// /// /// The value of this field is 10. /// public static readonly int DefaultRetryAttempts = 10; private static readonly Func DefaultDelayInSecondsByAttemptFunc = attempt => { var random = new Random(); return (int)Math.Round( Math.Pow(attempt - 1, 4) + 15 + random.Next(30) * attempt); }; private readonly ILog _logger = LogProvider.For(); private readonly object _lockObject = new object(); private int _attempts; private int[] _delaysInSeconds; private Func _delayInSecondsByAttemptFunc; private AttemptsExceededAction _onAttemptsExceeded; private bool _logEvents; private Type[] _onlyOn; private Type[] _exceptOn; /// /// Initializes a new instance of the /// class with number. /// public AutomaticRetryAttribute() { Attempts = DefaultRetryAttempts; DelayInSecondsByAttemptFunc = DefaultDelayInSecondsByAttemptFunc; LogEvents = true; OnAttemptsExceeded = AttemptsExceededAction.Fail; Order = 20; } /// /// Gets or sets the maximum number of automatic retry attempts. /// /// Any non-negative number. /// The value in a set operation is less than zero. public int Attempts { get { lock (_lockObject) { return _attempts; } } set { if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value), @"Attempts value must be equal or greater than zero."); } lock (_lockObject) { _attempts = value; } } } /// /// Gets or sets the delays between attempts. /// /// An array of non-negative numbers. /// The value in a set operation is null. /// The value contain one or more negative numbers. [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public int[] DelaysInSeconds { get { lock (_lockObject) { return _delaysInSeconds; } } set { if (value != null) { if (value.Length == 0) throw new ArgumentNullException(nameof(value)); if (value.Any(static delay => delay < 0)) throw new ArgumentException( $@"{nameof(DelaysInSeconds)} value must be an array of non-negative numbers.", nameof(value)); } lock (_lockObject) { _delaysInSeconds = value; } } } /// /// Gets or sets a function using to get a delay by an attempt number. /// /// The value in a set operation is null. [JsonIgnore] public Func DelayInSecondsByAttemptFunc { get { lock (_lockObject) { return _delayInSecondsByAttemptFunc;} } set { if (value == null) throw new ArgumentNullException(nameof(value)); lock (_lockObject) { _delayInSecondsByAttemptFunc = value; } } } /// /// Gets or sets a candidate state for a background job that /// will be chosen when number of retry attempts exceeded. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] public AttemptsExceededAction OnAttemptsExceeded { get { lock (_lockObject) { return _onAttemptsExceeded; } } set { lock (_lockObject) { _onAttemptsExceeded = value; } } } /// /// Gets or sets whether to produce log messages on retry attempts. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] [DefaultValue(true)] public bool LogEvents { get { lock (_lockObject) { return _logEvents; } } set { lock (_lockObject) { _logEvents = value; } } } /// /// Gets a sets an array of exception types that will be used to determine whether /// automatic retry logic should be attempted to run. By default it will be run on /// any exception, but this property allow to reduce it only to some specific /// exception types and their subtypes. /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public Type[] OnlyOn { get { lock (_lockObject) { return _onlyOn; } } set { lock (_lockObject) { _onlyOn = value; } } } /// /// Gets or sets the array of exception types on which the automatic retry mechanism /// should not be applied. /// /// /// An array of objects representing the exception types to /// be excluded from automatic retries. /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public Type[] ExceptOn { get { lock (_lockObject) { return _exceptOn; } } set { lock (_lockObject) { _exceptOn = value; } } } /// public void OnStateElection(ElectStateContext context) { var failedState = context.CandidateState as FailedState; if (failedState == null) { // This filter accepts only failed job state. return; } if (_onlyOn != null && _onlyOn.Length > 0) { var exceptionType = failedState.Exception.GetType(); var satisfied = false; foreach (var onlyOn in _onlyOn) { if (onlyOn.GetTypeInfo().IsAssignableFrom(exceptionType.GetTypeInfo())) { satisfied = true; break; } } if (!satisfied) return; } if (_exceptOn != null && _exceptOn.Length > 0) { var exceptionType = failedState.Exception.GetType(); var satisfied = true; foreach (var exceptOn in _exceptOn) { if (exceptOn.GetTypeInfo().IsAssignableFrom(exceptionType.GetTypeInfo())) { satisfied = false; break; } } if (!satisfied) return; } var retryAttempt = context.GetJobParameter("RetryCount", allowStale: true) + 1; if (retryAttempt <= Attempts) { ScheduleAgainLater(context, retryAttempt, failedState); } else if (retryAttempt > Attempts && OnAttemptsExceeded == AttemptsExceededAction.Delete) { TransitionToDeleted(context, failedState); } else { if (LogEvents) { _logger.ErrorException( $"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred.", failedState.Exception); } } } /// public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) { if (context.NewState is ScheduledState && context.NewState.Reason != null && context.NewState.Reason.StartsWith("Retry attempt", StringComparison.OrdinalIgnoreCase)) { transaction.AddToSet("retries", context.BackgroundJob.Id); } } /// public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction) { if (ScheduledState.StateName.Equals(context.OldStateName, StringComparison.OrdinalIgnoreCase) || FailedState.StateName.Equals(context.OldStateName, StringComparison.OrdinalIgnoreCase)) { transaction.RemoveFromSet("retries", context.BackgroundJob.Id); } } /// /// Schedules the job to run again later. See . /// /// The state context. /// The count of retry attempts made so far. /// Object which contains details about the current failed state. private void ScheduleAgainLater(ElectStateContext context, int retryAttempt, FailedState failedState) { context.SetJobParameter("RetryCount", retryAttempt); int delayInSeconds; if (_delaysInSeconds != null) { delayInSeconds = retryAttempt <= _delaysInSeconds.Length ? _delaysInSeconds[retryAttempt - 1] : _delaysInSeconds.Last(); } else { delayInSeconds = DelayInSecondsByAttemptFunc(retryAttempt); } var delay = TimeSpan.FromSeconds(delayInSeconds); const int maxMessageLength = 50; var exceptionMessage = failedState.Exception.Message.Length > maxMessageLength ? failedState.Exception.Message.Substring(0, maxMessageLength - 1) + "…" : failedState.Exception.Message; // If attempt number is less than max attempts, we should // schedule the job to run again later. var reason = $"Retry attempt {retryAttempt} of {Attempts}: {exceptionMessage}"; context.CandidateState = delay == TimeSpan.Zero ? (IState)new EnqueuedState { Reason = reason } : new ScheduledState(delay) { Reason = reason }; if (LogEvents) { _logger.WarnException( $"Failed to process the job '{context.BackgroundJob.Id}': an exception occurred. Retry attempt {retryAttempt} of {Attempts} will be performed in {delay}.", failedState.Exception); } } /// /// Transition the candidate state to the deleted state. /// /// The state context. /// Object which contains details about the current failed state. private void TransitionToDeleted(ElectStateContext context, FailedState failedState) { context.CandidateState = new DeletedState(new ExceptionInfo(failedState.Exception)) { Reason = Attempts > 0 ? "Exceeded the maximum number of retry attempts." : "Retries were disabled for this job." }; if (LogEvents) { _logger.WarnException( $"Failed to process the job '{context.BackgroundJob.Id}': an exception occured. Job was automatically deleted because the retry attempt count exceeded {Attempts}.", failedState.Exception); } } } } ================================================ FILE: src/Hangfire.Core/BackgroundJob.Instance.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; namespace Hangfire { partial class BackgroundJob { /// public BackgroundJob([NotNull] string id, [CanBeNull] Job job, DateTime createdAt) : this(id, job, createdAt, null) { } /// public BackgroundJob([NotNull] string id, [CanBeNull] Job job, DateTime createdAt, [CanBeNull] IReadOnlyDictionary parametersSnapshot) { if (id == null) throw new ArgumentNullException(nameof(id)); Id = id; Job = job; CreatedAt = createdAt; ParametersSnapshot = parametersSnapshot; } /// [NotNull] public string Id { get; } /// [CanBeNull] public Job Job { get; } /// public DateTime CreatedAt { get; } /// [CanBeNull] public IReadOnlyDictionary ParametersSnapshot { get; } } } ================================================ FILE: src/Hangfire.Core/BackgroundJob.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.States; namespace Hangfire { /// /// Provides static methods for creating fire-and-forget, delayed /// jobs and continuations as well as re-queue and delete existing /// background jobs. /// /// /// /// This class is a wrapper for the /// interface and its default implementation, /// class, that was created for the most simple scenarios. Please consider /// using the types above in real world applications. /// This class also contains undocumented constructor and instance /// members. They are hidden to not to confuse new users. You can freely /// use them in low-level API. /// /// /// /// /// /// public partial class BackgroundJob { private static readonly Func DefaultFactory = static () => new BackgroundJobClient(); private static readonly object ClientFactoryLock = new object(); private static Func _clientFactory; internal static Func ClientFactory { get { lock (ClientFactoryLock) { return _clientFactory ?? DefaultFactory; } } set { lock (ClientFactoryLock) { _clientFactory = value; } } } /// /// Creates a new fire-and-forget job based on a given method call expression. /// /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull, InstantHandle] Expression methodCall) { return ClientFactory().Enqueue(methodCall); } /// /// Creates a new fire-and-forget job based on a given method call expression and places it /// to the specified queue. /// /// Default queue for the background job. /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull] string queue, [NotNull, InstantHandle] Expression methodCall) { return ClientFactory().Enqueue(queue, methodCall); } /// /// Creates a new fire-and-forget job based on a given method call expression. /// /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull, InstantHandle] Expression> methodCall) { return ClientFactory().Enqueue(methodCall); } /// /// Creates a new fire-and-forget job based on a given method call expression and places it /// to the specified queue. /// /// Default queue for the background job. /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall) { return ClientFactory().Enqueue(queue, methodCall); } /// /// Creates a new fire-and-forget job based on a given method call expression. /// /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull, InstantHandle] Expression> methodCall) { return ClientFactory().Enqueue(methodCall); } /// /// Creates a new fire-and-forget job based on a given method call expression and places it /// to the specified queue. /// /// Default queue for the background job. /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall) { return ClientFactory().Enqueue(queue, methodCall); } /// /// Creates a new fire-and-forget job based on a given method call expression. /// /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull, InstantHandle] Expression> methodCall) { return ClientFactory().Enqueue(methodCall); } /// /// Creates a new fire-and-forget job based on a given method call expression and places it /// to the specified queue. /// /// Default queue for the background job. /// Method call expression that will be marshalled to a server. /// Unique identifier of a background job. /// /// /// is . /// /// /// /// public static string Enqueue([NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall) { return ClientFactory().Enqueue(queue, methodCall); } /// /// Creates a new background job based on a specified method /// call expression and schedules it to be enqueued after a given delay. /// /// /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull, InstantHandle] Expression methodCall, TimeSpan delay) { return ClientFactory().Schedule(methodCall, delay); } /// /// Creates a new background job based on a specified method call expression and schedules it /// to be enqueued to the specified queue after a given delay. /// /// /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, TimeSpan delay) { return ClientFactory().Schedule(queue, methodCall, delay); } /// /// Creates a new background job based on a specified method /// call expression and schedules it to be enqueued after a given delay. /// /// /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { return ClientFactory().Schedule(methodCall, delay); } /// /// Creates a new background job based on a specified method call expression and schedules it /// to be enqueued to the specified queue after a given delay. /// /// /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { return ClientFactory().Schedule(queue, methodCall, delay); } /// /// Creates a new background job based on a specified method call expression /// and schedules it to be enqueued at the given moment of time. /// /// /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull, InstantHandle] Expression methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(methodCall, enqueueAt); } /// /// Creates a new background job based on a specified method call expression and schedules it /// to be enqueued to the specified queue at the given moment of time. /// /// /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(queue, methodCall, enqueueAt); } /// /// Creates a new background job based on a specified method call expression /// and schedules it to be enqueued at the given moment of time. /// /// /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(methodCall, enqueueAt); } /// /// Creates a new background job based on a specified method call expression and schedules it /// to be enqueued to the specified queue at the given moment of time. /// /// /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(queue, methodCall, enqueueAt); } /// /// Creates a new background job based on a specified instance method /// call expression and schedules it to be enqueued after a given delay. /// /// /// Type whose method will be invoked during job processing. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { return ClientFactory().Schedule(methodCall, delay); } /// /// Creates a new background job based on a specified instance method call expression and schedules /// it to be enqueued to the specified queue after a given delay. /// /// /// Type whose method will be invoked during job processing. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { return ClientFactory().Schedule(queue, methodCall, delay); } /// /// Creates a new background job based on a specified instance method /// call expression and schedules it to be enqueued after a given delay. /// /// /// Type whose method will be invoked during job processing. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { return ClientFactory().Schedule(methodCall, delay); } /// /// Creates a new background job based on a specified instance method call expression and schedules /// it to be enqueued to the specified queue after a given delay. /// /// /// Type whose method will be invoked during job processing. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { return ClientFactory().Schedule(queue, methodCall, delay); } /// /// Creates a new background job based on a specified method call expression /// and schedules it to be enqueued at the given moment of time. /// /// /// The type whose method will be invoked during the job processing. /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(methodCall, enqueueAt); } /// /// Creates a new background job based on a specified method call expression and schedules it /// to be enqueued to the specified queue at the given moment of time. /// /// /// The type whose method will be invoked during the job processing. /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(queue, methodCall, enqueueAt); } /// /// Creates a new background job based on a specified method call expression /// and schedules it to be enqueued at the given moment of time. /// /// /// The type whose method will be invoked during the job processing. /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(methodCall, enqueueAt); } /// /// Creates a new background job based on a specified method call expression and schedules it /// to be enqueued to the specified queue at the given moment of time. /// /// /// The type whose method will be invoked during the job processing. /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// The moment of time at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { return ClientFactory().Schedule(queue, methodCall, enqueueAt); } /// /// Changes state of a job with the specified /// to the . /// /// /// /// An identifier, that will be used to find a job. /// True on a successful state transition, false otherwise. public static bool Delete([NotNull] string jobId) { return ClientFactory().Delete(jobId); } /// /// Changes state of a job with the specified /// to the . State change is only performed /// if current job state is equal to the value. /// /// /// /// Identifier of job, whose state is being changed. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Delete([NotNull] string jobId, [CanBeNull] string fromState) { return ClientFactory().Delete(jobId, fromState); } /// /// Changes state of a job with the specified /// to the . /// /// /// Identifier of job, whose state is being changed. /// True, if state change succeeded, otherwise false. public static bool Requeue([NotNull] string jobId) { return ClientFactory().Requeue(jobId); } /// /// Changes state of a job with the specified /// to the . If value /// is not null, state change will be performed only if the current state name /// of a job equal to the given value. /// /// /// Identifier of job, whose state is being changed. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Requeue([NotNull] string jobId, [CanBeNull] string fromState) { return ClientFactory().Requeue(jobId, fromState); } /// /// Changes state of a job with the specified /// to the . /// /// /// Identifier of job, whose state is being changed. /// Delay, after which the job will be scheduled. /// True, if state change succeeded, otherwise false. public static bool Reschedule([NotNull] string jobId, TimeSpan delay) { return ClientFactory().Reschedule(jobId, delay); } /// /// Changes state of a job with the specified /// to the . If value /// is not null, state change will be performed only if the current state name /// of a job equal to the given value. /// /// /// Identifier of job, whose state is being changed. /// Delay, after which the job will be scheduled. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Reschedule([NotNull] string jobId, TimeSpan delay, [CanBeNull] string fromState) { return ClientFactory().Reschedule(jobId, delay, fromState); } /// /// Changes state of a job with the specified /// to the . /// /// /// Identifier of job, whose state is being changed. /// The moment of time at which the job will be rescheduled. /// True, if state change succeeded, otherwise false. public static bool Reschedule([NotNull] string jobId, DateTimeOffset enqueueAt) { return ClientFactory().Reschedule(jobId, enqueueAt); } /// /// Changes state of a job with the specified /// to the . If value /// is not null, state change will be performed only if the current state name /// of a job equal to the given value. /// /// /// Identifier of job, whose state is being changed. /// The moment of time at which the job will be rescheduled. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Reschedule([NotNull] string jobId, DateTimeOffset enqueueAt, [CanBeNull] string fromState) { return ClientFactory().Reschedule(jobId, enqueueAt, fromState); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall) { return ContinueJobWith(parentId, methodCall); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall) { return ClientFactory().ContinueJobWith(parentId, methodCall); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall) { return ContinueJobWith(parentId, methodCall); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall) { return ClientFactory().ContinueJobWith(parentId, methodCall); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall, JobContinuationOptions options) { return ContinueJobWith(parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall, JobContinuationOptions options) { return ClientFactory().ContinueJobWith(parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be enqueued to the /// specified queue. /// /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ClientFactory().ContinueJobWith(parentId, queue, methodCall, options: options); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ContinueJobWith(parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ClientFactory().ContinueJobWith(parentId, methodCall, options: options); } /// /// Creates a new background job that will wait for another background job to be enqueued to the /// specified queue. /// /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ClientFactory().ContinueJobWith(parentId, queue, methodCall, options: options); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options) { return ContinueJobWith(parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options) { return ClientFactory().ContinueJobWith(parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be enqueued to the /// specified queue. /// /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ClientFactory().ContinueJobWith(parentId, queue, methodCall, options: options); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ContinueJobWith(parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be enqueued. /// /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ClientFactory().ContinueJobWith(parentId, methodCall, options: options); } /// /// Creates a new background job that will wait for another background job to be enqueued to the /// specified queue. /// /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] string parentId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ClientFactory().ContinueJobWith(parentId, queue, methodCall, options: options); } } } ================================================ FILE: src/Hangfire.Core/BackgroundJobClient.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; namespace Hangfire { /// /// Provides methods for creating background jobs and changing their states. /// Represents a default implementation of the /// interface. /// /// /// /// This class uses the interface /// for creating background jobs and the /// interface for changing their states. Please see documentation for those /// types and their implementations to learn the details. /// /// /// Despite the fact that instance methods of this class are thread-safe, /// most implementations of the interface are neither /// thread-safe, nor immutable. Please create a new instance of a state /// class for each operation to avoid race conditions and unexpected side /// effects. /// /// /// /// public class BackgroundJobClient : IBackgroundJobClient, IBackgroundJobClientV2 { private readonly JobStorage _storage; private readonly IBackgroundJobFactory _factory; private readonly IBackgroundJobStateChanger _stateChanger; /// /// Initializes a new instance of the /// class with the storage from a global configuration. /// /// /// /// Please see the class for the /// details regarding the global configuration. /// public BackgroundJobClient() : this(JobStorage.Current) { } /// /// Initializes a new instance of the /// class with the specified storage. /// /// /// Job storage to use for background jobs. /// /// is null. public BackgroundJobClient([NotNull] JobStorage storage) : this(storage, JobFilterProviders.Providers) { } /// /// Initializes a new instance of the class /// with the specified storage and filter provider. /// /// Job storage to use for background jobs. /// Filter provider responsible to locate job filters. /// is null. /// is null. public BackgroundJobClient([NotNull] JobStorage storage, [NotNull] IJobFilterProvider filterProvider) : this(storage, new BackgroundJobFactory(filterProvider), new BackgroundJobStateChanger(filterProvider)) { } /// /// Initializes a new instance of the class /// with the specified storage, background job factory and state changer. /// /// /// Job storage to use for background jobs. /// Factory to create background jobs. /// State changer to change states of background jobs. /// /// is null. /// is null. /// is null. public BackgroundJobClient( [NotNull] JobStorage storage, [NotNull] IBackgroundJobFactory factory, [NotNull] IBackgroundJobStateChanger stateChanger) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (factory == null) throw new ArgumentNullException(nameof(factory)); if (stateChanger == null) throw new ArgumentNullException(nameof(stateChanger)); _storage = storage; _stateChanger = stateChanger; _factory = factory; } /// public JobStorage Storage => _storage; public int RetryAttempts { get { if (_factory is BackgroundJobFactory factory) { return factory.RetryAttempts; } return 0; } set { if (_factory is BackgroundJobFactory factory) { factory.RetryAttempts = value; } } } /// public string Create(Job job, IState state) => Create(job, state, null); /// public string Create(Job job, IState state, IDictionary parameters) { if (job == null) throw new ArgumentNullException(nameof(job)); if (state == null) throw new ArgumentNullException(nameof(state)); try { using (var connection = _storage.GetConnection()) { var context = new CreateContext(_storage, connection, job, state, parameters); var backgroundJob = _factory.Create(context); return backgroundJob?.Id; } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new BackgroundJobClientException("Background job creation failed. See inner exception for details.", ex); } } /// public bool ChangeState(string jobId, IState state, string expectedState) { if (jobId == null) throw new ArgumentNullException(nameof(jobId)); if (state == null) throw new ArgumentNullException(nameof(state)); try { using (var connection = _storage.GetConnection()) { var appliedState = _stateChanger.ChangeState(new StateChangeContext( _storage, connection, jobId, state, expectedState != null ? new[] { expectedState } : null)); return appliedState != null && appliedState.Name.Equals(state.Name, StringComparison.OrdinalIgnoreCase); } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new BackgroundJobClientException("State change of a background job failed. See inner exception for details", ex); } } } } ================================================ FILE: src/Hangfire.Core/BackgroundJobClientException.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; using Hangfire.Client; #pragma warning disable 618 // Obsolete member namespace Hangfire { /// /// The exception that is thrown when an instance of the class that /// implements the interface is unable /// to perform an operation due to an error. /// #if !NETSTANDARD1_3 [Serializable] #endif public class BackgroundJobClientException : CreateJobFailedException { /// /// Initializes a new instance of the /// class with a specified error message and a reference to the inner exception /// that is the cause of this exception. /// /// The error message that explains the reason for the exception. /// The exception that is the cause of this exception, not null. public BackgroundJobClientException(string message, Exception inner) : base(message, inner) { } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected BackgroundJobClientException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif } } ================================================ FILE: src/Hangfire.Core/BackgroundJobClientExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Linq.Expressions; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.States; namespace Hangfire { /// /// Provides extension methods for the /// interface to simplify the creation of fire-and-forget jobs, delayed /// jobs, continuations and other background jobs in well-known states. /// Also allows to re-queue and delete existing background jobs. /// public static class BackgroundJobClientExtensions { /// /// Creates a background job based on a specified lambda expression /// and places it into its actual queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// A job client instance. /// Static method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new EnqueuedState()); } /// /// Creates a background job based on a specified lambda expression and places it into /// the specified queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// A job client instance. /// Default queue for the background job. /// Static method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new EnqueuedState()); } /// /// Creates a background job based on a specified lambda expression /// and places it into its actual queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// A job client instance. /// Static method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new EnqueuedState()); } /// /// Creates a background job based on a specified lambda expression and places it into /// the specified queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// A job client instance. /// Default queue for the background job. /// Static method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new EnqueuedState()); } /// /// Creates a background job based on a specified lambda expression /// and places it into its actual queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new EnqueuedState()); } /// /// Creates a background job based on a specified lambda expression and places it into /// the specified queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new EnqueuedState()); } /// /// Creates a background job based on a specified lambda expression /// and places it into its actual queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new EnqueuedState()); } /// /// Creates a background job based on a specified lambda expression and places it into /// the specified queue. /// Please, see the to learn how to /// place the job on a non-default queue. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Unique identifier of the created job. public static string Enqueue( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new EnqueuedState()); } /// /// Creates a new background job based on a specified lambda expression /// and schedules it to be enqueued after a given delay. /// /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified lambda expression and schedules it /// to be enqueued to the specified queue after a given delay. /// /// A job client instance. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified lambda expression /// and schedules it to be enqueued after a given delay. /// /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified lambda expression and schedules it /// to be enqueued to the specified queue after a given delay. /// /// A job client instance. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified lambda expression /// and schedules it to be enqueued at the specified moment of time. /// /// A job client instance. /// Method call expression that will be marshalled to the Server. /// Moment of time at which the job will be enqueued. /// Unique identifier or a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified lambda expression and schedules it /// to be enqueued to the specified queue at the specified moment of time. /// /// A job client instance. /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// Moment of time at which the job will be enqueued. /// Unique identifier or a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified lambda expression /// and schedules it to be enqueued at the specified moment of time. /// /// A job client instance. /// Method call expression that will be marshalled to the Server. /// Moment of time at which the job will be enqueued. /// Unique identifier or a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified lambda expression and schedules it /// to be enqueued to the specified queue at the specified moment of time. /// /// A job client instance. /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// Moment of time at which the job will be enqueued. /// Unique identifier or a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified instance method /// call expression and schedules it to be enqueued after a given delay. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified instance method call expression and /// schedules it to be enqueued to the specified queue after a given delay. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified instance method /// call expression and schedules it to be enqueued after a given delay. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified instance method call expression and /// schedules it to be enqueued to the specified queue after a given delay. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for the background job. /// Instance method call expression that will be marshalled to the Server. /// Delay, after which the job will be enqueued. /// Unique identifier of the created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, TimeSpan delay) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(delay)); } /// /// Creates a new background job based on a specified lambda expression and schedules /// it to be enqueued at the specified moment. /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Method call expression that will be marshalled to the Server. /// Moment at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified lambda expression and schedules /// it to be enqueued to the specified queue at the specified moment. /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// Moment at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified lambda expression and schedules /// it to be enqueued at the specified moment. /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Method call expression that will be marshalled to the Server. /// Moment at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified lambda expression and schedules /// it to be enqueued to the specified queue at the specified moment. /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for the background job. /// Method call expression that will be marshalled to the Server. /// Moment at which the job will be enqueued. /// Unique identifier of a created job. public static string Schedule( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, DateTimeOffset enqueueAt) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(queue, methodCall, new ScheduledState(enqueueAt.UtcDateTime)); } /// /// Creates a new background job based on a specified lambda expression in a given state. /// /// A job client instance. /// Static method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(Job.FromExpression(methodCall), state); } /// /// Creates a new background job based on a specified lambda expression in a given state with /// the specified default queue. /// /// A job client instance. /// Default queue for a job. /// Static method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); if (queue == null) throw new ArgumentNullException(nameof(queue)); return client.Create(Job.FromExpression(methodCall, queue), state); } /// /// Creates a new background job based on a specified lambda expression in a given state. /// /// A job client instance. /// Static method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(Job.FromExpression(methodCall), state); } /// /// Creates a new background job based on a specified lambda expression in a given state with /// the specified default queue. /// /// A job client instance. /// Default queue for a job. /// Static method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); if (queue == null) throw new ArgumentNullException(nameof(queue)); return client.Create(Job.FromExpression(methodCall, queue), state); } /// /// Creates a new background job based on a specified instance method in a given state. /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(Job.FromExpression(methodCall), state); } /// /// Creates a new background job based on a specified instance method in a given state with /// the specified default queue. /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for a job. /// Instance method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); if (queue == null) throw new ArgumentNullException(nameof(queue)); return client.Create(Job.FromExpression(methodCall, queue), state); } /// /// Creates a new background job based on a specified instance method in a given state. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Instance method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.Create(Job.FromExpression(methodCall), state); } /// /// Creates a new background job based on a specified instance method in a given state with /// the specified default queue. /// /// /// Type whose method will be invoked during job processing. /// A job client instance. /// Default queue for a job. /// Instance method call expression that will be marshalled to the Server. /// Initial state of a job. /// Unique identifier of the created job. public static string Create( [NotNull] this IBackgroundJobClient client, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); if (queue == null) throw new ArgumentNullException(nameof(queue)); return client.Create(Job.FromExpression(methodCall, queue), state); } /// /// Changes state of a job with the given to /// the specified one. /// /// /// An instance of implementation. /// A job, whose state is being changed. /// New state for a job. /// True, if state change succeeded, otherwise false. public static bool ChangeState( [NotNull] this IBackgroundJobClient client, [NotNull] string jobId, [NotNull] IState state) { if (client == null) throw new ArgumentNullException(nameof(client)); return client.ChangeState(jobId, state, null); } /// /// Changes state of a job with the specified /// to the . /// /// /// /// The job is not actually being deleted, this method changes only /// its state. /// /// This operation does not provide guarantee that the job will not be /// performed. If you are deleting a job that is performing right now, it /// will be performed anyway, despite of this call. /// /// The method returns result of a state transition. It can be false /// if a job was expired, its method does not exist or there was an /// exception during the state change process. /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// True, if state change succeeded, otherwise false. public static bool Delete([NotNull] this IBackgroundJobClient client, [NotNull] string jobId) { return Delete(client, jobId, null); } /// /// Changes state of a job with the specified /// to the . If value /// is not null, state change will be performed only if the current state name /// of a job equal to the given value. /// /// /// /// The job is not actually being deleted, this method changes only /// its state. /// /// This operation does not provide guarantee that the job will not be /// performed. If you are deleting a job that is performing right now, it /// will be performed anyway, despite of this call. /// /// The method returns result of a state transition. It can be false /// if a job was expired, its method does not exist or there was an /// exception during the state change process. /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Delete( [NotNull] this IBackgroundJobClient client, [NotNull] string jobId, [CanBeNull] string fromState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new DeletedState(); return client.ChangeState(jobId, state, fromState); } /// /// Changes state of a job with the specified /// to the . /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// True, if state change succeeded, otherwise false. public static bool Requeue([NotNull] this IBackgroundJobClient client, [NotNull] string jobId) { return Requeue(client, jobId, null); } /// /// Changes state of a job with the specified /// to the . If value /// is not null, state change will be performed only if the current state name /// of a job equal to the given value. /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// Delay, after which the job will be rescheduled. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Reschedule( [NotNull] this IBackgroundJobClient client, [NotNull] string jobId, TimeSpan delay, [CanBeNull] string fromState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new ScheduledState(delay); return client.ChangeState(jobId, state, fromState); } /// /// Changes state of a job with the specified /// to the . /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// Delay, after which the job will be rescheduled. /// True, if state change succeeded, otherwise false. public static bool Reschedule([NotNull] this IBackgroundJobClient client, [NotNull] string jobId, TimeSpan delay) { return Reschedule(client, jobId, delay, null); } /// /// Changes state of a job with the specified /// to the . If value /// is not null, state change will be performed only if the current state name /// of a job equal to the given value. /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// Moment of time at which the job will be rescheduled. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Reschedule( [NotNull] this IBackgroundJobClient client, [NotNull] string jobId, DateTimeOffset enqueueAt, [CanBeNull] string fromState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new ScheduledState(enqueueAt.UtcDateTime); return client.ChangeState(jobId, state, fromState); } /// /// Changes state of a job with the specified /// to the . /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// Moment of time at which the job will be rescheduled. /// True, if state change succeeded, otherwise false. public static bool Reschedule([NotNull] this IBackgroundJobClient client, [NotNull] string jobId, DateTimeOffset enqueueAt) { return Reschedule(client, jobId, enqueueAt, null); } /// /// Changes state of a job with the specified /// to the . If value /// is not null, state change will be performed only if the current state name /// of a job equal to the given value. /// /// /// An instance of implementation. /// Identifier of job, whose state is being changed. /// Current state assertion, or null if unneeded. /// True, if state change succeeded, otherwise false. public static bool Requeue( [NotNull] this IBackgroundJobClient client, [NotNull] string jobId, [CanBeNull] string fromState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new EnqueuedState(); return client.ChangeState(jobId, state, fromState); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall) { return ContinueJobWith(client, parentId, methodCall); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall) { return ContinueJobWith(client, parentId, methodCall, new EnqueuedState()); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall) { return ContinueJobWith(client, parentId, methodCall); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall) { return ContinueJobWith(client, parentId, methodCall, new EnqueuedState()); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall, [NotNull] IState nextState) { return ContinueJobWith(client, parentId, methodCall, nextState); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall, [NotNull] IState nextState) { return ContinueJobWith(client, parentId, methodCall, nextState, JobContinuationOptions.OnlyOnSucceededState); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState nextState) { return ContinueJobWith(client, parentId, methodCall, nextState); } /// /// Creates a new background job that will wait for a successful completion /// of another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState nextState) { return ContinueJobWith(client, parentId, methodCall, nextState, JobContinuationOptions.OnlyOnSucceededState); } /// /// Creates a new background job that will wait for another background job to be triggered /// in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall, JobContinuationOptions options) { return ContinueJobWith(client, parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be triggered /// in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression methodCall, JobContinuationOptions options) { return ContinueJobWith(client, parentId, methodCall, new EnqueuedState(), options); } /// /// Creates a new background job that will wait for another background job to be triggered /// in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options) { return ContinueJobWith(client, parentId, methodCall, options); } /// /// Creates a new background job that will wait for another background job to be triggered /// in the . /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, JobContinuationOptions options) { return ContinueJobWith(client, parentId, methodCall, new EnqueuedState(), options); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// Continuation options. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [InstantHandle] Expression methodCall, [NotNull] IState nextState, JobContinuationOptions options) { return ContinueJobWith(client, parentId, methodCall, nextState, options); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [InstantHandle] Expression methodCall, [NotNull] IState nextState, JobContinuationOptions options) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState, options); return client.Create(methodCall, state); } /// /// Creates a new background job that will wait for another background job to be enqueued /// to the specified queue. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState ?? new EnqueuedState(), options); return client.Create(queue, methodCall, state); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [InstantHandle] Expression> methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ContinueJobWith(client, parentId, methodCall, nextState, options); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [InstantHandle] Expression> methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState ?? new EnqueuedState(), options); return client.Create(methodCall, state); } /// /// Creates a new background job that will wait for another background job to be enqueued /// to the specified queue. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull] string queue, [InstantHandle] Expression> methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState ?? new EnqueuedState(), options); return client.Create(queue, methodCall, state); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// Continuation options. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState nextState, JobContinuationOptions options) { return ContinueJobWith(client, parentId, methodCall, nextState, options); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] IState nextState, JobContinuationOptions options) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState, options); return client.Create(methodCall, state); } /// /// Creates a new background job that will wait for another background job to be enqueued /// to the specified queue. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// Continuation options. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState ?? new EnqueuedState(), options); return client.Create(queue, methodCall, state); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. [Obsolete("Deprecated for clarity, please use ContinueJobWith method with the same arguments. Will be removed in 2.0.0.")] public static string ContinueWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { return ContinueJobWith(client, parentId, methodCall, nextState, options); } /// /// Creates a new background job that will wait for another background job to be triggered. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull, InstantHandle] Expression> methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState ?? new EnqueuedState(), options); return client.Create(methodCall, state); } /// /// Creates a new background job that will wait for another background job to be enqueued /// to the specified queue. /// /// A job client instance. /// Identifier of a background job to wait completion for. /// Default queue for the continuation. /// Method call expression that will be marshalled to a server. /// Next state for a job, when continuation is triggered. /// If null, then is used. /// Continuation options. By default, /// is used. /// Unique identifier of a created job. public static string ContinueJobWith( [NotNull] this IBackgroundJobClient client, [NotNull] string parentId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [CanBeNull] IState nextState = null, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState) { if (client == null) throw new ArgumentNullException(nameof(client)); var state = new AwaitingState(parentId, nextState ?? new EnqueuedState(), options); return client.Create(queue, methodCall, state); } } } ================================================ FILE: src/Hangfire.Core/BackgroundJobServer.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Server; using Hangfire.States; namespace Hangfire { public class BackgroundJobServer : IBackgroundProcessingServer { private readonly ILog _logger = LogProvider.For(); private readonly BackgroundJobServerOptions _options; private readonly BackgroundProcessingServer _processingServer; /// /// Initializes a new instance of the class /// with default options and storage. /// public BackgroundJobServer() : this(new BackgroundJobServerOptions()) { } /// /// Initializes a new instance of the class /// with default options and the given storage. /// /// The storage public BackgroundJobServer([NotNull] JobStorage storage) : this(new BackgroundJobServerOptions(), storage) { } /// /// Initializes a new instance of the class /// with the given options and storage. /// /// Server options public BackgroundJobServer([NotNull] BackgroundJobServerOptions options) : this(options, JobStorage.Current) { } /// /// Initializes a new instance of the class /// with the specified options and the given storage. /// /// Server options /// The storage public BackgroundJobServer([NotNull] BackgroundJobServerOptions options, [NotNull] JobStorage storage) : this(options, storage, Enumerable.Empty()) { } public BackgroundJobServer( [NotNull] BackgroundJobServerOptions options, [NotNull] JobStorage storage, [NotNull] IEnumerable additionalProcesses) #pragma warning disable 618 : this(options, storage, additionalProcesses, null, null, null, null, null) #pragma warning restore 618 { } [Obsolete("Create your own BackgroundJobServer-like type and pass custom services to it. This constructor will be removed in 2.0.0.")] [EditorBrowsable(EditorBrowsableState.Advanced)] public BackgroundJobServer( [NotNull] BackgroundJobServerOptions options, [NotNull] JobStorage storage, [NotNull] IEnumerable additionalProcesses, [CanBeNull] IJobFilterProvider filterProvider, [CanBeNull] JobActivator activator, [CanBeNull] IBackgroundJobFactory factory, [CanBeNull] IBackgroundJobPerformer performer, [CanBeNull] IBackgroundJobStateChanger stateChanger) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (options == null) throw new ArgumentNullException(nameof(options)); if (additionalProcesses == null) throw new ArgumentNullException(nameof(additionalProcesses)); _options = options; var processes = new List(); processes.AddRange(GetRequiredProcesses(filterProvider, activator, factory, performer, stateChanger)); processes.AddRange(additionalProcesses.Select(static x => x.UseBackgroundPool(1))); var properties = new Dictionary { { "Queues", options.Queues }, { "WorkerCount", options.WorkerCount } }; _logger.Info($"Starting Hangfire Server using job storage: '{storage}'"); storage.WriteOptionsToLog(_logger); _logger.Info("Using the following options for Hangfire Server:\r\n" + $" Worker count: {options.WorkerCount}\r\n" + $" Listening queues: {String.Join(", ", options.Queues.Select(static x => "'" + x + "'"))}\r\n" + $" Shutdown timeout: {options.ShutdownTimeout}\r\n" + $" Schedule polling interval: {options.SchedulePollingInterval}"); var wrongQueues = new HashSet(StringComparer.Ordinal); foreach (var queue in options.Queues) { if (!EnqueuedState.TryValidateQueueName(queue)) { wrongQueues.Add(queue); } } if (wrongQueues.Count > 0) { _logger.Warn($"These queues fail to match the naming format: {String.Join(", ", wrongQueues.Select(static x => $"'{x}'"))}. A queue name must consist of lowercase letters, digits, underscore, and dash characters only."); } _processingServer = new BackgroundProcessingServer( storage, processes, properties, GetProcessingServerOptions()); } public void SendStop() { _logger.Debug("Hangfire Server is stopping..."); _processingServer.SendStop(); } public void Dispose() { _processingServer.Dispose(); GC.SuppressFinalize(this); } [Obsolete("This method is a stub. There is no need to call the `Start` method. Will be removed in version 2.0.0.")] public void Start() { } [Obsolete("Please call the `Shutdown` method instead. Will be removed in version 2.0.0.")] public void Stop() { SendStop(); } [Obsolete("Please call the `Shutdown` method instead. Will be removed in version 2.0.0.")] public void Stop(bool force) { SendStop(); } public bool WaitForShutdown(TimeSpan timeout) { return _processingServer.WaitForShutdown(timeout); } public Task WaitForShutdownAsync(CancellationToken cancellationToken) { return _processingServer.WaitForShutdownAsync(cancellationToken); } private IEnumerable GetRequiredProcesses( [CanBeNull] IJobFilterProvider filterProvider, [CanBeNull] JobActivator activator, [CanBeNull] IBackgroundJobFactory factory, [CanBeNull] IBackgroundJobPerformer performer, [CanBeNull] IBackgroundJobStateChanger stateChanger) { var processes = new List(); var timeZoneResolver = _options.TimeZoneResolver ?? new DefaultTimeZoneResolver(); if (factory == null && performer == null && stateChanger == null) { filterProvider = filterProvider ?? _options.FilterProvider ?? JobFilterProviders.Providers; activator = activator ?? _options.Activator ?? JobActivator.Current; factory = new BackgroundJobFactory(filterProvider); performer = new BackgroundJobPerformer(filterProvider, activator, _options.TaskScheduler); stateChanger = new BackgroundJobStateChanger(filterProvider); } else { if (factory == null) throw new ArgumentNullException(nameof(factory)); if (performer == null) throw new ArgumentNullException(nameof(performer)); if (stateChanger == null) throw new ArgumentNullException(nameof(stateChanger)); } processes.Add(new Worker(_options.Queues, performer, stateChanger).UseBackgroundPool(_options.WorkerCount, _options.WorkerThreadConfigurationAction)); if (!_options.IsLightweightServer) { processes.Add( new DelayedJobScheduler(_options.SchedulePollingInterval, stateChanger) { TaskScheduler = _options.TaskScheduler, MaxDegreeOfParallelism = _options.MaxDegreeOfParallelismForSchedulers } .UseBackgroundPool(1)); processes.Add( new RecurringJobScheduler(factory, _options.SchedulePollingInterval, timeZoneResolver) { TaskScheduler = _options.TaskScheduler, MaxDegreeOfParallelism = _options.MaxDegreeOfParallelismForSchedulers } .UseBackgroundPool(1)); } return processes; } private BackgroundProcessingServerOptions GetProcessingServerOptions() { return new BackgroundProcessingServerOptions { StopTimeout = _options.StopTimeout, ShutdownTimeout = _options.ShutdownTimeout, HeartbeatInterval = _options.HeartbeatInterval, #pragma warning disable 618 ServerCheckInterval = _options.ServerWatchdogOptions?.CheckInterval ?? _options.ServerCheckInterval, ServerTimeout = _options.ServerWatchdogOptions?.ServerTimeout ?? _options.ServerTimeout, #pragma warning restore 618 CancellationCheckInterval = _options.CancellationCheckInterval, ServerName = _options.ServerName, ExcludeStorageProcesses = _options.IsLightweightServer }; } } } ================================================ FILE: src/Hangfire.Core/BackgroundJobServerOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Server; using Hangfire.States; namespace Hangfire { public class BackgroundJobServerOptions { // https://github.com/HangfireIO/Hangfire/issues/246 private const int MaxDefaultWorkerCount = 20; private int _workerCount; private string[] _queues; private TimeSpan _serverTimeout; private TimeSpan _serverCheckInterval; private TimeSpan _heartbeatInterval; private TimeSpan _stopTimeout; private TimeSpan _shutdownTimeout; private TimeSpan _schedulePollingInterval; public BackgroundJobServerOptions() { WorkerCount = Math.Min(Environment.ProcessorCount * 5, MaxDefaultWorkerCount); Queues = new[] { EnqueuedState.DefaultQueue }; StopTimeout = BackgroundProcessingServerOptions.DefaultStopTimeout; ShutdownTimeout = BackgroundProcessingServer.DefaultShutdownTimeout; SchedulePollingInterval = DelayedJobScheduler.DefaultPollingDelay; HeartbeatInterval = BackgroundProcessingServerOptions.DefaultHeartbeatInterval; ServerTimeout = ServerWatchdog.DefaultServerTimeout; ServerCheckInterval = ServerWatchdog.DefaultCheckInterval; CancellationCheckInterval = ServerJobCancellationWatcher.DefaultCheckInterval; FilterProvider = null; Activator = null; TimeZoneResolver = null; TaskScheduler = TaskScheduler.Default; } public string ServerName { get; set; } /// /// Gets or sets whether storage instance will include only and required /// and processes. No /// storage-related processes or recurring/delayed job schedulers will be included. /// public bool IsLightweightServer { get; set; } public int WorkerCount { get { return _workerCount; } set { if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value), "WorkerCount property value should be positive."); _workerCount = value; } } public string[] Queues { get { return _queues; } set { if (value == null) throw new ArgumentNullException(nameof(value)); if (value.Length == 0) throw new ArgumentException("You should specify at least one queue to listen.", nameof(value)); _queues = value; } } public TimeSpan StopTimeout { get => _stopTimeout; set { if ((value < TimeSpan.Zero && value != Timeout.InfiniteTimeSpan) || value.TotalMilliseconds > Int32.MaxValue) { throw new ArgumentOutOfRangeException(nameof(value), $"StopTimeout must be either equal to or less than {Int32.MaxValue} milliseconds and non-negative or infinite"); } _stopTimeout = value; } } public TimeSpan ShutdownTimeout { get { return _shutdownTimeout; } set { if ((value < TimeSpan.Zero && value != Timeout.InfiniteTimeSpan) || value.TotalMilliseconds > Int32.MaxValue) { throw new ArgumentOutOfRangeException(nameof(value), $"ShutdownTimeout must be either equal to or less than {Int32.MaxValue} milliseconds and non-negative or infinite"); } _shutdownTimeout = value; } } public TimeSpan SchedulePollingInterval { get { return _schedulePollingInterval; } set { if (value < TimeSpan.Zero || value.TotalMilliseconds > Int32.MaxValue) { throw new ArgumentOutOfRangeException(nameof(value), $"SchedulePollingInterval must be non-negative and either equal to or less than {Int32.MaxValue} milliseconds"); } _schedulePollingInterval = value; } } public TimeSpan HeartbeatInterval { get { return _heartbeatInterval; } set { if (value < TimeSpan.Zero || value > ServerWatchdog.MaxHeartbeatInterval) { throw new ArgumentOutOfRangeException(nameof(value), $"HeartbeatInterval must be either non-negative and equal to or less than {ServerWatchdog.MaxHeartbeatInterval.Hours} hours"); } _heartbeatInterval = value; } } public TimeSpan ServerCheckInterval { get { return _serverCheckInterval; } set { if (value < TimeSpan.Zero || value > ServerWatchdog.MaxServerCheckInterval) { throw new ArgumentOutOfRangeException(nameof(value), $"ServerCheckInterval must be either non-negative and equal to or less than {ServerWatchdog.MaxServerCheckInterval.Hours} hours"); } _serverCheckInterval = value; } } public TimeSpan ServerTimeout { get { return _serverTimeout; } set { if (value < TimeSpan.Zero || value > ServerWatchdog.MaxServerTimeout) { throw new ArgumentOutOfRangeException(nameof(value), $"ServerTimeout must be either non-negative and equal to or less than {ServerWatchdog.MaxServerTimeout.Hours} hours"); } _serverTimeout = value; } } public TimeSpan CancellationCheckInterval { get; set; } [Obsolete("Please use `ServerTimeout` or `ServerCheckInterval` options instead. Will be removed in 2.0.0.")] public ServerWatchdogOptions ServerWatchdogOptions { get; set; } [CanBeNull] public IJobFilterProvider FilterProvider { get; set; } [CanBeNull] public JobActivator Activator { get; set; } [CanBeNull] public ITimeZoneResolver TimeZoneResolver { get; set; } [CanBeNull] public TaskScheduler TaskScheduler { get; set; } [CanBeNull] public Action WorkerThreadConfigurationAction { get; set; } /// /// Experimental option for schedulers, but not for workers. Gets or sets the /// maximum degree of parallelism for /// and processes, allowing them to enable /// parallel scheduling of recurring and delayed jobs when the specified value /// is greater than 1. Parallel work items are executed on the task scheduler /// specified in the property. /// public int MaxDegreeOfParallelismForSchedulers { get; set; } = 1; } } ================================================ FILE: src/Hangfire.Core/CaptureCultureAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Globalization; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Server; namespace Hangfire { public sealed class CaptureCultureAttribute : JobFilterAttribute, IClientFilter, IServerFilter { private readonly ILog _logger = LogProvider.GetLogger(typeof(CaptureCultureAttribute)); public CaptureCultureAttribute() : this(null) { } public CaptureCultureAttribute([CanBeNull] string defaultCultureName, bool captureDefault = true) : this(defaultCultureName, defaultCultureName, captureDefault) { } public CaptureCultureAttribute( [CanBeNull] string defaultCultureName, [CanBeNull] string defaultUICultureName, bool captureDefault = true) { DefaultCultureName = defaultCultureName; DefaultUICultureName = defaultUICultureName; CaptureDefault = captureDefault; #if !NETSTANDARD1_3 // For backward compatibility, the cached method does not respect user-overridden values. // https://blog.codeinside.eu/2018/05/28/cultureinfo-getculture-vs-new-cultureinfo/ // https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.-ctor#system-globalization-cultureinfo-ctor(system-string) CachedCulture = false; #endif } [CanBeNull] public string DefaultCultureName { get; } [CanBeNull] public string DefaultUICultureName { get; } public bool CaptureDefault { get; } #if !NETSTANDARD1_3 /// /// Gets or sets whether to use the method when getting /// a culture by its name, or create a instance using its /// constructor instead. Cached method does not respect user-overridden values associated /// with the current culture specified on the OS level. /// public bool CachedCulture { get; set; } #endif public void OnCreating(CreatingContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var currentCulture = CultureInfo.CurrentCulture; var currentUICulture = CultureInfo.CurrentUICulture; if (CaptureDefault == false && currentCulture.Name.Equals(DefaultCultureName, StringComparison.Ordinal)) { // Don't set the 'CurrentCulture' job parameter when it's equal to the default one } else { context.SetJobParameter("CurrentCulture", currentCulture.Name); } if (CaptureDefault == false && currentUICulture.Name.Equals(DefaultUICultureName, StringComparison.Ordinal)) { // Don't set the 'CurrentUICulture' job parameter when it's equal to the default one } else if (GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_180) && currentUICulture.Equals(currentCulture)) { // Don't set the 'CurrentUICulture' when it's the same as 'CurrentCulture' under // CompatibilityLevel.Version_180 } else { context.SetJobParameter("CurrentUICulture", currentUICulture.Name); } } public void OnCreated(CreatedContext context) { } public void OnPerforming(PerformingContext context) { var cultureName = context.GetJobParameter("CurrentCulture", allowStale: true); var uiCultureName = context.GetJobParameter("CurrentUICulture", allowStale: true) ?? cultureName; cultureName = cultureName ?? DefaultCultureName; uiCultureName = uiCultureName ?? DefaultUICultureName; try { if (cultureName != null) { context.Items["PreviousCulture"] = CultureInfo.CurrentCulture; SetCurrentCulture(GetCultureInfo(cultureName)); } } catch (CultureNotFoundException ex) { // TODO: Make this overridable, and start with throwing an exception _logger.WarnException($"Unable to set CurrentCulture for job {context.BackgroundJob.Id} due to an exception", ex); } try { if (uiCultureName != null) { context.Items["PreviousUICulture"] = CultureInfo.CurrentUICulture; SetCurrentUICulture(GetCultureInfo(uiCultureName)); } } catch (CultureNotFoundException ex) { // TODO: Make this overridable, and start with throwing an exception _logger.WarnException($"Unable to set CurrentUICulture for job {context.BackgroundJob.Id} due to an exception", ex); } } public void OnPerformed(PerformedContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (context.Items.TryGetValue("PreviousCulture", out var culture)) { SetCurrentCulture((CultureInfo)culture); } if (context.Items.TryGetValue("PreviousUICulture", out var uiCulture)) { SetCurrentUICulture((CultureInfo)uiCulture); } } private static void SetCurrentCulture(CultureInfo value) { #if !NETSTANDARD1_3 System.Threading.Thread.CurrentThread.CurrentCulture = value; #else CultureInfo.CurrentCulture = value; #endif } // ReSharper disable once InconsistentNaming private static void SetCurrentUICulture(CultureInfo value) { #if !NETSTANDARD1_3 System.Threading.Thread.CurrentThread.CurrentUICulture = value; #else CultureInfo.CurrentUICulture = value; #endif } [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")] private CultureInfo GetCultureInfo(string cultureName) { #if !NETSTANDARD1_3 if (CachedCulture) { return CultureInfo.GetCultureInfo(cultureName); } #endif return new CultureInfo(cultureName); } } } ================================================ FILE: src/Hangfire.Core/Client/BackgroundJobFactory.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Profiling; using Hangfire.States; namespace Hangfire.Client { public class BackgroundJobFactory : IBackgroundJobFactory { private readonly IJobFilterProvider _filterProvider; private readonly IBackgroundJobFactory _innerFactory; public BackgroundJobFactory() : this(JobFilterProviders.Providers) { } public BackgroundJobFactory([NotNull] IJobFilterProvider filterProvider) : this(filterProvider, new CoreBackgroundJobFactory(new StateMachine(filterProvider))) { } public int RetryAttempts { get { if (_innerFactory is CoreBackgroundJobFactory factory) { return factory.RetryAttempts; } return 0; } set { if (_innerFactory is CoreBackgroundJobFactory factory) { factory.RetryAttempts = value; } } } internal BackgroundJobFactory( [NotNull] IJobFilterProvider filterProvider, [NotNull] IBackgroundJobFactory innerFactory) { if (filterProvider == null) throw new ArgumentNullException(nameof(filterProvider)); if (innerFactory == null) throw new ArgumentNullException(nameof(innerFactory)); _filterProvider = filterProvider; _innerFactory = innerFactory; } public IStateMachine StateMachine => _innerFactory.StateMachine; public BackgroundJob Create(CreateContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var filterInfo = GetFilters(context.Job); try { context.Factory = this; var createdContext = CreateWithFilters(context, filterInfo.ClientFilters); return createdContext.BackgroundJob; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { var exceptionContext = new ClientExceptionContext(context, ex); InvokeExceptionFilters(exceptionContext, filterInfo.ClientExceptionFiltersReversed); if (!exceptionContext.ExceptionHandled) { throw; } return null; } finally { context.Factory = null; } } private JobFilterInfo GetFilters(Job job) { return new JobFilterInfo(_filterProvider.GetFilters(job)); } private CreatedContext CreateWithFilters( CreateContext context, JobFilterInfo.FilterCollection filters) { var preContext = new CreatingContext(context); var enumerator = filters.GetEnumerator(); return InvokeNextClientFilter(ref enumerator, _innerFactory, context, preContext); } private static CreatedContext InvokeNextClientFilter( ref JobFilterInfo.FilterCollection.Enumerator enumerator, IBackgroundJobFactory innerFactory, CreateContext context, CreatingContext preContext) { if (enumerator.MoveNext()) { return InvokeClientFilter(ref enumerator, innerFactory, context, preContext); } var backgroundJob = innerFactory.Create(context); return new CreatedContext(context, backgroundJob, false, null); } private static CreatedContext InvokeClientFilter( ref JobFilterInfo.FilterCollection.Enumerator enumerator, IBackgroundJobFactory innerFactory, CreateContext context, CreatingContext preContext) { var filter = enumerator.Current!; preContext.Profiler.InvokeMeasured( new KeyValuePair(filter, preContext), InvokeOnCreating, static ctx => $"OnCreating for {ctx.Value.Job.Type.FullName}.{ctx.Value.Job.Method.Name}"); if (preContext.Canceled) { return new CreatedContext(preContext, null, true, null); } var wasError = false; CreatedContext postContext; try { postContext = InvokeNextClientFilter(ref enumerator, innerFactory, context, preContext); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { wasError = true; postContext = new CreatedContext(preContext, null, false, ex); postContext.Profiler.InvokeMeasured( new KeyValuePair(filter, postContext), InvokeOnCreated, static ctx => $"OnCreated for {ctx.Value.BackgroundJob?.Id ?? "(null)"}"); if (!postContext.ExceptionHandled) { throw; } } if (!wasError) { postContext.Profiler.InvokeMeasured( new KeyValuePair(filter, postContext), InvokeOnCreated, static ctx => $"OnCreated for {ctx.Value.BackgroundJob?.Id ?? "(null)"}"); } return postContext; } private static void InvokeOnCreating(KeyValuePair x) { try { x.Key.OnCreating(x.Value); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ex.PreserveOriginalStackTrace(); throw; } } private static void InvokeOnCreated(KeyValuePair x) { try { x.Key.OnCreated(x.Value); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ex.PreserveOriginalStackTrace(); throw; } } private static void InvokeExceptionFilters( ClientExceptionContext context, JobFilterInfo.ReversedFilterCollection filters) { foreach (var filter in filters) { context.Profiler.InvokeMeasured( new KeyValuePair(filter, context), InvokeOnClientException, static ctx => $"OnClientException for {ctx.Value.Job.Type.FullName}.{ctx.Value.Job.Method.Name}"); } } private static void InvokeOnClientException(KeyValuePair x) { try { x.Key.OnClientException(x.Value); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ex.PreserveOriginalStackTrace(); throw; } } } } ================================================ FILE: src/Hangfire.Core/Client/ClientExceptionContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Client { /// /// Provides the context for the /// method of the interface. /// public class ClientExceptionContext : CreateContext { public ClientExceptionContext(CreateContext createContext, Exception exception) : base(createContext) { if (exception == null) throw new ArgumentNullException(nameof(exception)); Exception = exception; } /// /// Gets an exception that occurred during the creation of the job. /// public Exception Exception { get; } /// /// Gets or sets a value that indicates that this /// object handles an exception occurred during the creation of the job. /// public bool ExceptionHandled { get; set; } } } ================================================ FILE: src/Hangfire.Core/Client/CoreBackgroundJobFactory.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Runtime.ExceptionServices; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Logging; using Hangfire.States; using Hangfire.Storage; namespace Hangfire.Client { internal sealed class CoreBackgroundJobFactory : IBackgroundJobFactory { private readonly ILog _logger = LogProvider.GetLogger(typeof(CoreBackgroundJobFactory)); private readonly object _syncRoot = new object(); private int _retryAttempts; private Func _retryDelayFunc; public CoreBackgroundJobFactory([NotNull] IStateMachine stateMachine) { StateMachine = stateMachine ?? throw new ArgumentNullException(nameof(stateMachine)); RetryAttempts = 0; RetryDelayFunc = GetRetryDelay; } public IStateMachine StateMachine { get; } public int RetryAttempts { get { lock (_syncRoot) { return _retryAttempts; } } set { lock (_syncRoot) { _retryAttempts = value; } } } public Func RetryDelayFunc { get { lock (_syncRoot) { return _retryDelayFunc; } } set { lock (_syncRoot) { _retryDelayFunc = value; } } } public BackgroundJob Create(CreateContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (context.Job.Queue != null && !context.Storage.HasFeature(JobStorageFeatures.JobQueueProperty)) { throw new NotSupportedException("Current storage doesn't support specifying queues directly for a specific job. Please use the QueueAttribute instead."); } var parameters = context.Parameters.ToDictionary( static x => x.Key, static x => SerializationHelper.Serialize(x.Value, SerializationOption.User)); var createdAt = DateTime.UtcNow; var expireIn = TimeSpan.FromDays(30); return CreateBackgroundJobTwoSteps(context, parameters, createdAt, expireIn); } private BackgroundJob CreateBackgroundJobTwoSteps(CreateContext context, Dictionary parameters, DateTime createdAt, TimeSpan expireIn) { var attemptsLeft = Math.Max(RetryAttempts, 0); // Retry may cause multiple background jobs to be created, especially when there's // a timeout-related exception. But initialization attempt will be performed only // for the most recent job, leaving all the previous ones in a non-initialized state // and making them invisible to other parts of the system, since no one knows their // identifiers. Since they also will be eventually expired leaving no trace, we can // consider that only one background job is created, regardless of retry attempts // number. var jobId = RetryOnException( ref attemptsLeft, static (_, ctx) => ctx.Context.Connection.CreateExpiredJob( ctx.Context.Job, ctx.Parameters, ctx.CreatedAt, ctx.ExpireIn), new JobCreateContext { Context = context, Parameters = parameters, CreatedAt = createdAt, ExpireIn = expireIn }); if (String.IsNullOrEmpty(jobId)) { return null; } var backgroundJob = new BackgroundJob(jobId, context.Job, createdAt, parameters); if (context.InitialState != null) { RetryOnException(ref attemptsLeft, static (attempt, ctx) => { if (attempt > 0) { // Normally, a distributed lock should be applied when making a retry, since // it's possible to get a timeout exception, when transaction was actually // committed. But since background job can't be returned to a position where // its state is null, and since only the current thread knows the job's identifier // when its state is null, and since we shouldn't do anything when it's non-null, // there will be no any race conditions. var data = ctx.Context.Connection.GetJobData(ctx.BackgroundJob.Id); if (data == null) throw new InvalidOperationException($"Was unable to initialize a background job '{ctx.BackgroundJob.Id}', because it doesn't exists."); if (!String.IsNullOrEmpty(data.State)) return; } using (var transaction = ctx.Context.Connection.CreateWriteTransaction()) { var applyContext = new ApplyStateContext( ctx.Context.Storage, ctx.Context.Connection, transaction, ctx.BackgroundJob, ctx.Context.InitialState!, oldStateName: null, ctx.Context.Profiler, ctx.StateMachine); ctx.StateMachine.ApplyState(applyContext); transaction.Commit(); } }, new JobInitializeContext { Context = context, StateMachine = StateMachine, BackgroundJob = backgroundJob }); } return backgroundJob; } private void RetryOnException(ref int attemptsLeft, Action action, TContext context) { RetryOnException(ref attemptsLeft, static (attempt, ctx) => { ctx.Key(attempt, ctx.Value); return true; }, new KeyValuePair, TContext>(action, context)); } private TResult RetryOnException(ref int attemptsLeft, Func action, TContext context) { List exceptions = null; var attempt = 0; var delay = TimeSpan.Zero; do { try { if (delay > TimeSpan.Zero) { Thread.Sleep(delay); } return action(attempt++, context); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { (exceptions ??= new List()).Add(ex); _logger.DebugException("An exception occurred while creating a background job, see inner exception for details.", ex); delay = RetryDelayFunc(attempt); } } while (attemptsLeft-- > 0); if (exceptions.Count == 1) { ExceptionDispatchInfo.Capture(exceptions[0]).Throw(); } throw new AggregateException(exceptions); } private static TimeSpan GetRetryDelay(int retryAttempt) { switch (retryAttempt) { case 1: return TimeSpan.Zero; case 2: return TimeSpan.FromMilliseconds(50); case 3: return TimeSpan.FromMilliseconds(100); default: return TimeSpan.FromMilliseconds(500); } } private readonly record struct JobCreateContext { public required CreateContext Context { get; init; } public required Dictionary Parameters { get; init; } public required DateTime CreatedAt { get; init; } public required TimeSpan ExpireIn { get; init; } } private readonly record struct JobInitializeContext { public required CreateContext Context { get; init; } public required IStateMachine StateMachine { get; init; } public required BackgroundJob BackgroundJob { get; init; } } } } ================================================ FILE: src/Hangfire.Core/Client/CreateContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; namespace Hangfire.Client { /// /// Provides information about the context in which the job is created. /// public class CreateContext { public CreateContext([NotNull] CreateContext context) : this(context.Storage, context.Connection, context.Job, context.InitialState, context.Parameters, context.Profiler, context.Items) { Factory = context.Factory; } public CreateContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] Job job, [CanBeNull] IState initialState) : this(storage, connection, job, initialState, null) { } public CreateContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] Job job, [CanBeNull] IState initialState, [CanBeNull] IDictionary parameters) : this(storage, connection, job, initialState, parameters, EmptyProfiler.Instance, null) { } internal CreateContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] Job job, [CanBeNull] IState initialState, [CanBeNull] IDictionary parameters, [NotNull] IProfiler profiler, [CanBeNull] IDictionary items) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (connection == null) throw new ArgumentNullException(nameof(connection)); if (job == null) throw new ArgumentNullException(nameof(job)); Storage = storage; Connection = connection; Job = job; InitialState = initialState; Profiler = profiler; Items = items ?? new Dictionary(); Parameters = parameters ?? new Dictionary(); } [NotNull] public JobStorage Storage { get; } [NotNull] public IStorageConnection Connection { get; } /// /// Gets an instance of the key-value storage. You can use it /// to pass additional information between different client filters /// or just between different methods. /// [NotNull] public IDictionary Items { get; } [NotNull] public virtual IDictionary Parameters { get; } [NotNull] public Job Job { get; } /// /// Gets the initial state of the creating job. Note, that /// the final state of the created job could be changed after /// the registered instances of the /// class are doing their job. /// [CanBeNull] public IState InitialState { get; } [NotNull] internal IProfiler Profiler { get; } [CanBeNull] public IBackgroundJobFactory Factory { get; internal set; } } } ================================================ FILE: src/Hangfire.Core/Client/CreatedContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Hangfire.Annotations; namespace Hangfire.Client { /// /// Provides the context for the /// method of the interface. /// public class CreatedContext : CreateContext { public CreatedContext( [NotNull] CreateContext context, [CanBeNull] BackgroundJob backgroundJob, bool canceled, [CanBeNull] Exception exception) : base(context) { BackgroundJob = backgroundJob; Canceled = canceled; Exception = exception; } [CanBeNull] [Obsolete("Please use `BackgroundJob` property instead. Will be removed in 2.0.0.")] public string JobId => BackgroundJob?.Id; [CanBeNull] public BackgroundJob BackgroundJob { get; } public override IDictionary Parameters => new ReadOnlyDictionary(base.Parameters); /// /// Gets an exception that occurred during the creation of the job. /// [CanBeNull] public Exception Exception { get; } /// /// Gets a value that indicates that this /// object was canceled. /// public bool Canceled { get; } /// /// Gets or sets a value that indicates that this /// object handles an exception occurred during the creation of the job. /// public bool ExceptionHandled { get; set; } [Obsolete("This method only throws InvalidOperationException, will be removed in 2.0.0.")] public void SetJobParameter([NotNull] string name, object value) { if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); throw new InvalidOperationException("Could not set parameter for a created job."); } } } ================================================ FILE: src/Hangfire.Core/Client/CreatingContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Client { /// /// Provides the context for the /// method of the interface. /// public class CreatingContext : CreateContext { public CreatingContext(CreateContext context) : base(context) { } /// /// Gets or sets a value that indicates that this /// object was canceled. /// public bool Canceled { get; set; } /// /// Sets the job parameter of the specified /// to the corresponding . The value of the /// parameter is serialized to a JSON string. /// /// /// The name of the parameter. /// The value of the parameter. /// /// The is null or empty. public void SetJobParameter(string name, object value) { if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); Parameters[name] = value; } /// /// Gets the job parameter of the specified /// if it exists. The parameter is deserialized from a JSON /// string value to the given type . /// /// /// The type of the parameter. /// The name of the parameter. /// The value of the given parameter if it exists or null otherwise. /// /// The is null or empty. /// Could not deserialize the parameter value to the type . public T GetJobParameter(string name) { if (String.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); try { return Parameters.TryGetValue(name, out var parameter) ? (T)parameter : default(T); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new InvalidOperationException( $"Could not get a value of the job parameter `{name}`. See inner exception for details.", ex); } } } } ================================================ FILE: src/Hangfire.Core/Client/IBackgroundJobFactory.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Annotations; using Hangfire.States; namespace Hangfire.Client { /// /// This interface acts as extensibility point for the process /// of job creation. See the default implementation in the /// class. /// public interface IBackgroundJobFactory { /// /// Gets a state machine that's responsible for initial state change. /// [NotNull] IStateMachine StateMachine { get; } /// /// Runs the process of job creation with the specified context. /// [CanBeNull] BackgroundJob Create([NotNull] CreateContext context); } } ================================================ FILE: src/Hangfire.Core/Client/IClientExceptionFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Client { /// /// Defines methods that are required for the client exception filter. /// public interface IClientExceptionFilter { /// /// Called when an exception occurred during the creation of the job. /// /// The filter context. void OnClientException(ClientExceptionContext filterContext); } } ================================================ FILE: src/Hangfire.Core/Client/IClientFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Client { /// /// Defines methods that are required for a client filter. /// public interface IClientFilter { /// /// Called before the creation of the job. /// /// The filter context. void OnCreating(CreatingContext context); /// /// Called after the creation of the job. /// /// The filter context. void OnCreated(CreatedContext context); } } ================================================ FILE: src/Hangfire.Core/Common/CachedExpressionCompiler.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Hangfire.Common { /// /// The caching expression tree compiler was copied from MVC core to MVC Futures so that Futures code could benefit /// from it and so that it could be exposed as a public API. This is the only public entry point into the system. /// See the comments in the ExpressionUtil namespace for more information. /// /// The unit tests for the ExpressionUtil.* types are in the System.Web.Mvc.Test project. /// [ExcludeFromCodeCoverage] internal static class CachedExpressionCompiler { private static readonly ParameterExpression UnusedParameterExpr = Expression.Parameter(typeof(object), "_unused"); /// /// Evaluates an expression (not a LambdaExpression), e.g. 2 + 2. /// /// /// Expression result. public static object Evaluate(Expression arg) { if (arg == null) { throw new ArgumentNullException(nameof(arg)); } Func func = Wrap(arg); return func(null); } private static Func Wrap(Expression arg) { var lambdaExpr = Expression.Lambda>(Expression.Convert(arg, typeof(object)), UnusedParameterExpr); return ExpressionUtil.CachedExpressionCompiler.Process(lambdaExpr); } } } ================================================ FILE: src/Hangfire.Core/Common/CancellationTokenExtentions.cs ================================================ // This file is part of Hangfire. Copyright © 2018 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Diagnostics; using System.Threading; using Hangfire.Logging; namespace Hangfire.Common { public static class CancellationTokenExtentions { /// /// Returns a class that contains a that is set, when /// the given is canceled. This method is based /// on cancellation token registration and avoids using the /// property as it may lead to high CPU issues. /// public static CancellationEvent GetCancellationEvent(this CancellationToken cancellationToken) { return new CancellationEvent(cancellationToken); } /// /// Performs a wait until the specified is elapsed or the /// given cancellation token is canceled and throw /// exception if wait succeeded. The wait is performed on a dedicated event /// wait handle to avoid using the property /// that may lead to high CPU issues. /// public static void WaitOrThrow(this CancellationToken cancellationToken, TimeSpan timeout) { if (Wait(cancellationToken, timeout)) { throw new OperationCanceledException(cancellationToken); } } /// /// Performs a wait until the specified is elapsed or the /// given cancellation token is canceled. The wait is performed on a dedicated event /// wait handle to avoid using the property /// that may lead to high CPU issues. /// public static bool Wait(this CancellationToken cancellationToken, TimeSpan timeout) { using var cancellationEvent = GetCancellationEvent(cancellationToken); var stopwatch = Stopwatch.StartNew(); var waitResult = cancellationEvent.WaitHandle.WaitOne(timeout); stopwatch.Stop(); var timeoutThreshold = TimeSpan.FromMilliseconds(1000); var elapsedThreshold = TimeSpan.FromMilliseconds(500); var protectionTime = TimeSpan.FromSeconds(1); // There was a precedent of the following message logged when CancellationToken.WaitHandle // was used directly, instead of having our custom CancellationEvent class used, please see // https://github.com/HangfireIO/Hangfire/issues/2447 if (!cancellationToken.IsCancellationRequested && timeout >= timeoutThreshold && stopwatch.Elapsed < elapsedThreshold) { try { var logger = LogProvider.GetLogger(typeof(CancellationTokenExtentions)); logger.Error($"Actual wait time for non-canceled token was '{stopwatch.Elapsed.TotalMilliseconds}' ms instead of '{timeout.TotalMilliseconds}' ms, wait result: {waitResult}, using protective wait. Please report this to Hangfire developers."); } finally { Thread.Sleep(protectionTime); } } return waitResult; } public sealed class CancellationEvent : IDisposable { private static readonly Action SetEventCallback = SetEvent; private readonly ManualResetEvent _mre; private CancellationTokenRegistration _registration; public CancellationEvent(CancellationToken cancellationToken) { _mre = new ManualResetEvent(false); _registration = cancellationToken.Register(SetEventCallback, _mre); } public EventWaitHandle WaitHandle => _mre; public void Dispose() { _registration.Dispose(); _mre.Dispose(); } private static void SetEvent(object state) { try { ((ManualResetEvent)state).Set(); } catch (ObjectDisposedException) { // When our event instance is already disposed, we already // aren't interested in any notifications. This statement // is just to ensure we don't throw any exceptions. } } } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/BinaryExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // BinaryExpression fingerprint class // Useful for things like array[index] [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class BinaryExpressionFingerprint : ExpressionFingerprint { public BinaryExpressionFingerprint(ExpressionType nodeType, Type type, MethodInfo method) : base(nodeType, type) { // Other properties on BinaryExpression (like IsLifted / IsLiftedToNull) are simply derived // from Type and NodeType, so they're not necessary for inclusion in the fingerprint. Method = method; } // http://msdn.microsoft.com/en-us/library/system.linq.expressions.binaryexpression.method.aspx public MethodInfo Method { get; } public override bool Equals(object obj) { BinaryExpressionFingerprint other = obj as BinaryExpressionFingerprint; return (other != null) && Equals(Method, other.Method) && Equals(other); } internal override void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddObject(Method); base.AddToHashCodeCombiner(combiner); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/CachedExpressionCompiler.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; // ReSharper disable All namespace Hangfire.Common.ExpressionUtil { [ExcludeFromCodeCoverage] internal static class CachedExpressionCompiler { // This is the entry point to the cached expression compilation system. The system // will try to turn the expression into an actual delegate as quickly as possible, // relying on cache lookups and other techniques to save time if appropriate. // If the provided expression is particularly obscure and the system doesn't know // how to handle it, we'll just compile the expression as normal. public static Func Process(Expression> lambdaExpression) { return Compiler.Compile(lambdaExpression); } private static class Compiler { private static Func _identityFunc; private static readonly ConcurrentDictionary> _simpleMemberAccessDict = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> _constMemberAccessDict = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> _fingerprintedCache = new ConcurrentDictionary>(); public static Func Compile(Expression> expr) { return CompileFromIdentityFunc(expr) ?? CompileFromConstLookup(expr) ?? CompileFromMemberAccess(expr) ?? CompileFromFingerprint(expr) ?? CompileSlow(expr); } private static Func CompileFromConstLookup(Expression> expr) { ConstantExpression constExpr = expr.Body as ConstantExpression; if (constExpr != null) { // model => {const} TOut constantValue = (TOut)constExpr.Value; return _ => constantValue; } return null; } private static Func CompileFromIdentityFunc(Expression> expr) { if (expr.Body == expr.Parameters[0]) { // model => model // don't need to lock, as all identity funcs are identical return _identityFunc ?? (_identityFunc = expr.Compile()); } return null; } private static Func CompileFromFingerprint(Expression> expr) { List capturedConstants; ExpressionFingerprintChain fingerprint = FingerprintingExpressionVisitor.GetFingerprintChain(expr, out capturedConstants); if (fingerprint != null) { var del = _fingerprintedCache.GetOrAdd(fingerprint, _ => { // Fingerprinting succeeded, but there was a cache miss. Rewrite the expression // and add the rewritten expression to the cache. var hoistedExpr = HoistingExpressionVisitor.Hoist(expr); return hoistedExpr.Compile(); }); return model => del(model, capturedConstants); } // couldn't be fingerprinted return null; } private static Func CompileFromMemberAccess(Expression> expr) { // Performance tests show that on the x64 platform, special-casing static member and // captured local variable accesses is faster than letting the fingerprinting system // handle them. On the x86 platform, the fingerprinting system is faster, but only // by around one microsecond, so it's not worth it to complicate the logic here with // an architecture check. MemberExpression memberExpr = expr.Body as MemberExpression; if (memberExpr != null) { if (memberExpr.Expression == expr.Parameters[0] || memberExpr.Expression == null) { // model => model.Member or model => StaticMember return _simpleMemberAccessDict.GetOrAdd(memberExpr.Member, _ => expr.Compile()); } ConstantExpression constExpr = memberExpr.Expression as ConstantExpression; if (constExpr != null) { // model => {const}.Member (captured local variable) var del = _constMemberAccessDict.GetOrAdd(memberExpr.Member, _ => { // rewrite as capturedLocal => ((TDeclaringType)capturedLocal).Member var constParamExpr = Expression.Parameter(typeof(object), "capturedLocal"); var constCastExpr = Expression.Convert(constParamExpr, memberExpr.Member.DeclaringType); var newMemberAccessExpr = memberExpr.Update(constCastExpr); var newLambdaExpr = Expression.Lambda>(newMemberAccessExpr, constParamExpr); return newLambdaExpr.Compile(); }); object capturedLocal = constExpr.Value; return _ => del(capturedLocal); } } return null; } private static Func CompileSlow(Expression> expr) { // fallback compilation system - just compile the expression directly return expr.Compile(); } } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/ConditionalExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // ConditionalExpression fingerprint class // Expression of form (test) ? ifTrue : ifFalse [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class ConditionalExpressionFingerprint : ExpressionFingerprint { public ConditionalExpressionFingerprint(ExpressionType nodeType, Type type) : base(nodeType, type) { // There are no properties on ConditionalExpression that are worth including in // the fingerprint. } public override bool Equals(object obj) { ConditionalExpressionFingerprint other = obj as ConditionalExpressionFingerprint; return (other != null) && Equals(other); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/ConstantExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // ConstantExpression fingerprint class // // A ConstantExpression might represent a captured local variable, so we can't compile // the value directly into the cached function. Instead, a placeholder is generated // and the value is hoisted into a local variables array. This placeholder can then // be compiled and cached, and the array lookup happens at runtime. [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class ConstantExpressionFingerprint : ExpressionFingerprint { public ConstantExpressionFingerprint(ExpressionType nodeType, Type type) : base(nodeType, type) { // There are no properties on ConstantExpression that are worth including in // the fingerprint. } public override bool Equals(object obj) { ConstantExpressionFingerprint other = obj as ConstantExpressionFingerprint; return (other != null) && Equals(other); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/DefaultExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // DefaultExpression fingerprint class // Expression of form default(T) [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class DefaultExpressionFingerprint : ExpressionFingerprint { public DefaultExpressionFingerprint(ExpressionType nodeType, Type type) : base(nodeType, type) { // There are no properties on DefaultExpression that are worth including in // the fingerprint. } public override bool Equals(object obj) { DefaultExpressionFingerprint other = obj as DefaultExpressionFingerprint; return (other != null) && Equals(other); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/ExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Hangfire.Common.ExpressionUtil { // Serves as the base class for all expression fingerprints. Provides a default implementation // of GetHashCode(). [ExcludeFromCodeCoverage] internal abstract class ExpressionFingerprint { protected ExpressionFingerprint(ExpressionType nodeType, Type type) { NodeType = nodeType; Type = type; } // the type of expression node, e.g. OP_ADD, MEMBER_ACCESS, etc. public ExpressionType NodeType { get; } // the CLR type resulting from this expression, e.g. int, string, etc. public Type Type { get; } internal virtual void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddInt32((int)NodeType); combiner.AddObject(Type); } protected bool Equals(ExpressionFingerprint other) { return (other != null) && (NodeType == other.NodeType) && Type == other.Type; } public override bool Equals(object obj) { return Equals(obj as ExpressionFingerprint); } public override int GetHashCode() { HashCodeCombiner combiner = new HashCodeCombiner(); AddToHashCodeCombiner(combiner); return combiner.CombinedHash; } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/ExpressionFingerprintChain.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; // ReSharper disable All namespace Hangfire.Common.ExpressionUtil { // Expression fingerprint chain class // Contains information used for generalizing, comparing, and recreating Expression instances // // Since Expression objects are immutable and are recreated for every invocation of an expression // helper method, they can't be compared directly. Fingerprinting Expression objects allows // information about them to be abstracted away, and the fingerprints can be directly compared. // Consider the process of fingerprinting that all values (parameters, constants, etc.) are hoisted // and replaced with dummies. What remains can be decomposed into a sequence of operations on specific // types and specific inputs. // // Some sample fingerprints chains: // // 2 + 4 -> OP_ADD, CONST:int, NULL, CONST:int // 2 + 8 -> OP_ADD, CONST:int, NULL, CONST:int // 2.0 + 4.0 -> OP_ADD, CONST:double, NULL, CONST:double // // 2 + 4 and 2 + 8 have the same fingerprint, but 2.0 + 4.0 has a different fingerprint since its // underlying types differ. Note that this looks a bit like prefix notation and is a side effect // of how the ExpressionVisitor class recurses into expressions. (Occasionally there will be a NULL // in the fingerprint chain, which depending on context can denote a static member, a null Conversion // in a BinaryExpression, and so forth.) // // "Hello " + "world" -> OP_ADD, CONST:string, NULL, CONST:string // "Hello " + {model} -> OP_ADD, CONST:string, NULL, PARAM_0:string // // These string concatenations have different fingerprints since the inputs are provided differently: // one is a constant, the other is a parameter. // // ({model} ?? "sample").Length -> MEMBER_ACCESS(String.Length), OP_COALESCE, PARAM_0:string, NULL, CONST:string // ({model} ?? "other sample").Length -> MEMBER_ACCESS(String.Length), OP_COALESCE, PARAM_0:string, NULL, CONST:string // // These expressions have the same fingerprint since all constants of the same underlying type are // treated equally. // // It's also important that the fingerprints don't reference the actual Expression objects that were // used to generate them, as the fingerprints will be cached, and caching a fingerprint that references // an Expression will root the Expression (and any objects it references). [ExcludeFromCodeCoverage] internal sealed class ExpressionFingerprintChain : IEquatable { public readonly List Elements = new List(); public bool Equals(ExpressionFingerprintChain other) { // Two chains are considered equal if two elements appearing in the same index in // each chain are equal (value equality, not referential equality). if (Elements.Count != other?.Elements.Count) { return false; } for (int i = 0; i < Elements.Count; i++) { if (!Equals(Elements[i], other.Elements[i])) { return false; } } return true; } public override bool Equals(object obj) { return Equals(obj as ExpressionFingerprintChain); } public override int GetHashCode() { HashCodeCombiner combiner = new HashCodeCombiner(); Elements.ForEach(combiner.AddFingerprint); return combiner.CombinedHash; } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/FingerprintingExpressionVisitor.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Hangfire.Common.ExpressionUtil { // This is a visitor which produces a fingerprint of an expression. It doesn't // rewrite the expression in a form which can be compiled and cached. [ExcludeFromCodeCoverage] internal sealed class FingerprintingExpressionVisitor : ExpressionVisitor { private readonly List _seenConstants = new List(); private readonly List _seenParameters = new List(); private readonly ExpressionFingerprintChain _currentChain = new ExpressionFingerprintChain(); private bool _gaveUp; private FingerprintingExpressionVisitor() { } private T GiveUp(T node) { // We don't understand this node, so just quit. _gaveUp = true; return node; } // Returns the fingerprint chain + captured constants list for this expression, or null // if the expression couldn't be fingerprinted. public static ExpressionFingerprintChain GetFingerprintChain(Expression expr, out List capturedConstants) { FingerprintingExpressionVisitor visitor = new FingerprintingExpressionVisitor(); visitor.Visit(expr); if (visitor._gaveUp) { capturedConstants = null; return null; } else { capturedConstants = visitor._seenConstants; return visitor._currentChain; } } public override Expression Visit(Expression node) { if (node == null) { _currentChain.Elements.Add(null); return null; } else { return base.Visit(node); } } protected override Expression VisitBinary(BinaryExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new BinaryExpressionFingerprint(node.NodeType, node.Type, node.Method)); return base.VisitBinary(node); } protected override Expression VisitBlock(BlockExpression node) { return GiveUp(node); } protected override CatchBlock VisitCatchBlock(CatchBlock node) { return GiveUp(node); } protected override Expression VisitConditional(ConditionalExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new ConditionalExpressionFingerprint(node.NodeType, node.Type)); return base.VisitConditional(node); } protected override Expression VisitConstant(ConstantExpression node) { if (_gaveUp) { return node; } _seenConstants.Add(node.Value); _currentChain.Elements.Add(new ConstantExpressionFingerprint(node.NodeType, node.Type)); return base.VisitConstant(node); } protected override Expression VisitDebugInfo(DebugInfoExpression node) { return GiveUp(node); } protected override Expression VisitDefault(DefaultExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new DefaultExpressionFingerprint(node.NodeType, node.Type)); return base.VisitDefault(node); } #if !NETSTANDARD1_3 protected override Expression VisitDynamic(DynamicExpression node) { return GiveUp(node); } #endif protected override ElementInit VisitElementInit(ElementInit node) { return GiveUp(node); } protected override Expression VisitExtension(Expression node) { return GiveUp(node); } protected override Expression VisitGoto(GotoExpression node) { return GiveUp(node); } protected override Expression VisitIndex(IndexExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new IndexExpressionFingerprint(node.NodeType, node.Type, node.Indexer)); return base.VisitIndex(node); } protected override Expression VisitInvocation(InvocationExpression node) { return GiveUp(node); } protected override Expression VisitLabel(LabelExpression node) { return GiveUp(node); } protected override LabelTarget VisitLabelTarget(LabelTarget node) { return GiveUp(node); } protected override Expression VisitLambda(Expression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new LambdaExpressionFingerprint(node.NodeType, node.Type)); return base.VisitLambda(node); } protected override Expression VisitListInit(ListInitExpression node) { return GiveUp(node); } protected override Expression VisitLoop(LoopExpression node) { return GiveUp(node); } protected override Expression VisitMember(MemberExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new MemberExpressionFingerprint(node.NodeType, node.Type, node.Member)); return base.VisitMember(node); } protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) { return GiveUp(node); } protected override MemberBinding VisitMemberBinding(MemberBinding node) { return GiveUp(node); } protected override Expression VisitMemberInit(MemberInitExpression node) { return GiveUp(node); } protected override MemberListBinding VisitMemberListBinding(MemberListBinding node) { return GiveUp(node); } protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node) { return GiveUp(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new MethodCallExpressionFingerprint(node.NodeType, node.Type, node.Method)); return base.VisitMethodCall(node); } protected override Expression VisitNew(NewExpression node) { return GiveUp(node); } protected override Expression VisitNewArray(NewArrayExpression node) { return GiveUp(node); } protected override Expression VisitParameter(ParameterExpression node) { if (_gaveUp) { return node; } int parameterIndex = _seenParameters.IndexOf(node); if (parameterIndex < 0) { // first time seeing this parameter parameterIndex = _seenParameters.Count; _seenParameters.Add(node); } _currentChain.Elements.Add(new ParameterExpressionFingerprint(node.NodeType, node.Type, parameterIndex)); return base.VisitParameter(node); } protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) { return GiveUp(node); } protected override Expression VisitSwitch(SwitchExpression node) { return GiveUp(node); } protected override SwitchCase VisitSwitchCase(SwitchCase node) { return GiveUp(node); } protected override Expression VisitTry(TryExpression node) { return GiveUp(node); } protected override Expression VisitTypeBinary(TypeBinaryExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new TypeBinaryExpressionFingerprint(node.NodeType, node.Type, node.TypeOperand)); return base.VisitTypeBinary(node); } protected override Expression VisitUnary(UnaryExpression node) { if (_gaveUp) { return node; } _currentChain.Elements.Add(new UnaryExpressionFingerprint(node.NodeType, node.Type, node.Method)); return base.VisitUnary(node); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/HashCodeCombiner.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Diagnostics.CodeAnalysis; namespace Hangfire.Common.ExpressionUtil { // based on System.Web.Util.HashCodeCombiner [ExcludeFromCodeCoverage] internal sealed class HashCodeCombiner { private long _combinedHash64 = 0x1505L; public int CombinedHash => _combinedHash64.GetHashCode(); public void AddFingerprint(ExpressionFingerprint fingerprint) { if (fingerprint != null) { fingerprint.AddToHashCodeCombiner(this); } else { AddInt32(0); } } public void AddEnumerable(IEnumerable e) { if (e == null) { AddInt32(0); } else { int count = 0; foreach (object o in e) { AddObject(o); count++; } AddInt32(count); } } public void AddInt32(int i) { _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i; } public void AddObject(object o) { int hashCode = o?.GetHashCode() ?? 0; AddInt32(hashCode); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/Hoisted.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; namespace Hangfire.Common.ExpressionUtil { internal delegate TValue Hoisted(TModel model, List capturedConstants); } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/HoistingExpressionVisitor.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; // ReSharper disable All namespace Hangfire.Common.ExpressionUtil { // This is a visitor which rewrites constant expressions as parameter lookups. It's meant // to produce an expression which can be cached safely. [ExcludeFromCodeCoverage] internal sealed class HoistingExpressionVisitor : ExpressionVisitor { private static readonly ParameterExpression _hoistedConstantsParamExpr = Expression.Parameter(typeof(List), "hoistedConstants"); private int _numConstantsProcessed; // factory will create instance private HoistingExpressionVisitor() { } public static Expression> Hoist(Expression> expr) { // rewrite Expression> as Expression> var visitor = new HoistingExpressionVisitor(); var rewrittenBodyExpr = visitor.Visit(expr.Body); var rewrittenLambdaExpr = Expression.Lambda>(rewrittenBodyExpr, expr.Parameters[0], _hoistedConstantsParamExpr); return rewrittenLambdaExpr; } protected override Expression VisitConstant(ConstantExpression node) { // rewrite the constant expression as (TConst)hoistedConstants[i]; // coverity[missing_super_call] return Expression.Convert(Expression.Property(_hoistedConstantsParamExpr, "Item", Expression.Constant(_numConstantsProcessed++)), node.Type); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/IndexExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // IndexExpression fingerprint class // Represents certain forms of array access or indexer property access [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class IndexExpressionFingerprint : ExpressionFingerprint { public IndexExpressionFingerprint(ExpressionType nodeType, Type type, PropertyInfo indexer) : base(nodeType, type) { // Other properties on IndexExpression (like the argument count) are simply derived // from Type and Indexer, so they're not necessary for inclusion in the fingerprint. Indexer = indexer; } // http://msdn.microsoft.com/en-us/library/system.linq.expressions.indexexpression.indexer.aspx public PropertyInfo Indexer { get; } public override bool Equals(object obj) { IndexExpressionFingerprint other = obj as IndexExpressionFingerprint; return (other != null) && Equals(Indexer, other.Indexer) && Equals(other); } internal override void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddObject(Indexer); base.AddToHashCodeCombiner(combiner); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/LambdaExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // LambdaExpression fingerprint class // Represents a lambda expression (root element in Expression) [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class LambdaExpressionFingerprint : ExpressionFingerprint { public LambdaExpressionFingerprint(ExpressionType nodeType, Type type) : base(nodeType, type) { // There are no properties on LambdaExpression that are worth including in // the fingerprint. } public override bool Equals(object obj) { LambdaExpressionFingerprint other = obj as LambdaExpressionFingerprint; return (other != null) && Equals(other); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/MemberExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // MemberExpression fingerprint class // Expression of form xxx.FieldOrProperty [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class MemberExpressionFingerprint : ExpressionFingerprint { public MemberExpressionFingerprint(ExpressionType nodeType, Type type, MemberInfo member) : base(nodeType, type) { Member = member; } // http://msdn.microsoft.com/en-us/library/system.linq.expressions.memberexpression.member.aspx public MemberInfo Member { get; } public override bool Equals(object obj) { MemberExpressionFingerprint other = obj as MemberExpressionFingerprint; return (other != null) && Equals(Member, other.Member) && Equals(other); } internal override void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddObject(Member); base.AddToHashCodeCombiner(combiner); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/MethodCallExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // MethodCallExpression fingerprint class // Expression of form xxx.Foo(...), xxx[...] (get_Item()), etc. [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class MethodCallExpressionFingerprint : ExpressionFingerprint { public MethodCallExpressionFingerprint(ExpressionType nodeType, Type type, MethodInfo method) : base(nodeType, type) { // Other properties on MethodCallExpression (like the argument count) are simply derived // from Type and Indexer, so they're not necessary for inclusion in the fingerprint. Method = method; } // http://msdn.microsoft.com/en-us/library/system.linq.expressions.methodcallexpression.method.aspx public MethodInfo Method { get; } public override bool Equals(object obj) { MethodCallExpressionFingerprint other = obj as MethodCallExpressionFingerprint; return (other != null) && Equals(Method, other.Method) && Equals(other); } internal override void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddObject(Method); base.AddToHashCodeCombiner(combiner); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/ParameterExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // ParameterExpression fingerprint class // Can represent the model parameter or an inner parameter in an open lambda expression [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class ParameterExpressionFingerprint : ExpressionFingerprint { public ParameterExpressionFingerprint(ExpressionType nodeType, Type type, int parameterIndex) : base(nodeType, type) { ParameterIndex = parameterIndex; } // Parameter position within the overall expression, used to maintain alpha equivalence. public int ParameterIndex { get; } public override bool Equals(object obj) { ParameterExpressionFingerprint other = obj as ParameterExpressionFingerprint; return (other != null) && (ParameterIndex == other.ParameterIndex) && Equals(other); } internal override void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddInt32(ParameterIndex); base.AddToHashCodeCombiner(combiner); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/TypeBinaryExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // TypeBinary fingerprint class // Expression of form "obj is T" [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class TypeBinaryExpressionFingerprint : ExpressionFingerprint { public TypeBinaryExpressionFingerprint(ExpressionType nodeType, Type type, Type typeOperand) : base(nodeType, type) { TypeOperand = typeOperand; } // http://msdn.microsoft.com/en-us/library/system.linq.expressions.typebinaryexpression.typeoperand.aspx public Type TypeOperand { get; } public override bool Equals(object obj) { TypeBinaryExpressionFingerprint other = obj as TypeBinaryExpressionFingerprint; return (other != null) && TypeOperand == other.TypeOperand && Equals(other); } internal override void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddObject(TypeOperand); base.AddToHashCodeCombiner(combiner); } } } ================================================ FILE: src/Hangfire.Core/Common/ExpressionUtil/UnaryExpressionFingerprint.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; #pragma warning disable 659 // overrides AddToHashCodeCombiner instead namespace Hangfire.Common.ExpressionUtil { // UnaryExpression fingerprint class // The most common appearance of a UnaryExpression is a cast or other conversion operator [SuppressMessage("Microsoft.Usage", "CA2218:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [SuppressMessage("SonarLint", "S1206:OverrideGetHashCodeOnOverridingEquals", Justification = "Overrides AddToHashCodeCombiner() instead.")] [ExcludeFromCodeCoverage] internal sealed class UnaryExpressionFingerprint : ExpressionFingerprint { public UnaryExpressionFingerprint(ExpressionType nodeType, Type type, MethodInfo method) : base(nodeType, type) { // Other properties on UnaryExpression (like IsLifted / IsLiftedToNull) are simply derived // from Type and NodeType, so they're not necessary for inclusion in the fingerprint. Method = method; } // http://msdn.microsoft.com/en-us/library/system.linq.expressions.unaryexpression.method.aspx public MethodInfo Method { get; } public override bool Equals(object obj) { UnaryExpressionFingerprint other = obj as UnaryExpressionFingerprint; return (other != null) && Equals(Method, other.Method) && Equals(other); } internal override void AddToHashCodeCombiner(HashCodeCombiner combiner) { combiner.AddObject(Method); base.AddToHashCodeCombiner(combiner); } } } ================================================ FILE: src/Hangfire.Core/Common/IJobFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Common { /// /// Defines members that specify the order of filters and /// whether multiple filters are allowed. /// public interface IJobFilter { /// /// When implemented in a class, gets or sets a value /// that indicates whether multiple filters are allowed. /// bool AllowMultiple { get; } /// /// When implemented in a class, gets the filter order. /// int Order { get; } } } ================================================ FILE: src/Hangfire.Core/Common/IJobFilterProvider.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; namespace Hangfire.Common { /// /// Provides an interface for finding filters. /// public interface IJobFilterProvider { /// /// Returns an enumerator that contains all the . /// /// /// /// The enumerator that contains all the . /// IEnumerable GetFilters(Job job); } } ================================================ FILE: src/Hangfire.Core/Common/Job.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Hangfire.Annotations; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Hangfire.States; namespace Hangfire.Common { /// /// Represents an action that can be marshalled to another process to /// be performed. /// /// /// /// The ability to serialize an action is the cornerstone of /// marshalling it outside of a current process boundaries. We are leaving /// behind all the tricky features, e.g. serializing lambdas with their /// closures or so, and considering a simple method call information as /// a such an action, and using reflection to perform it. /// /// Reflection-based method invocation requires an instance of /// the class, the arguments and an instance of /// the type on which to invoke the method (unless it is static). Since the /// same instance can be shared across multiple /// types (especially when they are defined in interfaces), we also allow /// to specify a that contains the defined method /// explicitly for better flexibility. /// /// Marshalling imposes restrictions on a method that should be /// performed: /// /// /// Method should be public. /// Method should not contain and parameters. /// Method should not contain open generic parameters. /// /// /// /// /// The following example demonstrates the creation of a /// type instances using expression trees. This is the recommended way of /// creating jobs. /// /// /// /// The next example demonstrates unsupported methods. Any attempt /// to create a job based on these methods fails with /// . /// /// /// /// /// /// /// /// public partial class Job { private static readonly object[] EmptyObjectArray = []; private static readonly ConcurrentDictionary AsyncStateMachineAttributeCache = new(); /// /// Initializes a new instance of the class with the /// metadata of a method with no arguments. /// /// /// Method that should be invoked. /// /// argument is null. /// is not supported. public Job([NotNull] MethodInfo method) : this(method, EmptyObjectArray) { } /// /// Initializes a new instance of the class with the /// metadata of a method and the given list of arguments. /// /// /// Method that should be invoked. /// Arguments that will be passed to a method invocation. /// /// argument is null. /// Parameter/argument count mismatch. /// is not supported. public Job([NotNull] MethodInfo method, [NotNull] params object[] args) // ReSharper disable once AssignNullToNotNullAttribute : this(method.DeclaringType, method, args) { } /// /// Initializes a new instance of the class with the /// type, metadata of a method with no arguments. /// /// /// Type that contains the given method. /// Method that should be invoked. /// /// argument is null. /// argument is null. /// /// does not contain the given . /// /// Parameter/argument count mismatch. /// is not supported. public Job([NotNull] Type type, [NotNull] MethodInfo method) : this(type, method, EmptyObjectArray) { } /// /// Initializes a new instance of the class with the /// type, metadata of a method, and the given list of arguments. /// /// /// Type that contains the given method. /// Method that should be invoked. /// Arguments that should be passed during the method call. /// /// argument is null. /// argument is null. /// argument is null. /// /// does not contain the given . /// /// Parameter/argument count mismatch. /// is not supported. public Job([NotNull] Type type, [NotNull] MethodInfo method, [NotNull] params object[] args) : this(type, method, args, null) { } /// /// Initializes a new instance of the class with the type, metadata of a method, /// and the given list of arguments, specified in a read-only list. /// /// /// Type that contains the given method. /// Method that should be invoked. /// Arguments that should be passed during the method call. /// /// argument is null. /// argument is null. /// argument is null. /// /// does not contain the given . /// /// Parameter/argument count mismatch. /// is not supported. public Job([NotNull] Type type, [NotNull] MethodInfo method, [NotNull] IReadOnlyList args) : this(type, method, args, null) { } /// /// Initializes a new instance of the class with the type, metadata of a method, /// the given list of arguments, and the default target queue for a job. /// /// /// Type that contains the given method. /// Method that should be invoked. /// Arguments that should be passed during the method call. /// Default target queue for the job. /// /// argument is null. /// argument is null. /// argument is null. /// /// does not contain the given . /// /// Parameter/argument count mismatch. /// is not supported. public Job([NotNull] Type type, [NotNull] MethodInfo method, [NotNull] IReadOnlyList args, [CanBeNull] string queue) { if (type == null) throw new ArgumentNullException(nameof(type)); if (method == null) throw new ArgumentNullException(nameof(method)); if (args == null) throw new ArgumentNullException(nameof(args)); Validate(type, nameof(type), method, nameof(method), args.Count, nameof(args)); if (queue != null) { EnqueuedState.ValidateQueueName(nameof(queue), queue); } Type = type; Method = method; Args = args; Queue = queue; } /// /// Gets the metadata of a type that contains a method that should be /// invoked during the performance. /// [NotNull] public Type Type { get; } /// /// Gets the metadata of a method that should be invoked during the /// performance. /// [NotNull] public MethodInfo Method { get; } /// /// Gets a read-only collection of arguments that Should be passed to a /// method invocation during the performance. /// [NotNull] public IReadOnlyList Args { get; } /// /// Gets a default target queue for a job to which it will be enqueued unless /// overriden by a job filter. /// [CanBeNull] public string Queue { get; } public override string ToString() { return ToString(includeQueue: true); } public string ToString(bool includeQueue) { var sb = new StringBuilder() .Append(Type.ToGenericTypeString()) .Append('.') .Append(Method.Name); if (includeQueue && Queue != null) { sb.Append(" (").Append(Queue).Append(')'); } return sb.ToString(); } internal IEnumerable GetTypeFilterAttributes(bool useCache) { return useCache ? ReflectedAttributeCache.GetTypeFilterAttributes(Type) : GetFilterAttributes(Type.GetTypeInfo()); } internal IEnumerable GetMethodFilterAttributes(bool useCache) { return useCache ? ReflectedAttributeCache.GetMethodFilterAttributes(Method) : GetFilterAttributes(Method); } private static IEnumerable GetFilterAttributes(MemberInfo memberInfo) { return memberInfo.GetCustomAttributes(inherit: true); } /// /// Gets a new instance of the class based on the /// given expression tree of a method call. /// /// /// Expression tree of a method call. /// /// is null. /// /// expression body is not of type /// . /// /// expression contains a method that is not supported. /// /// instance object of a given expression points to . /// /// /// /// The property of a returning job will /// point to the type of a given instance object when it is specified, /// or to the declaring type otherwise. All the arguments are evaluated /// using the expression compiler that uses caching where possible to /// decrease the performance penalty. /// /// Instance object (e.g. () => instance.Method()) is /// only used to obtain the type for a job. It is not /// serialized and not passed across the process boundaries. /// public static Job FromExpression([NotNull, InstantHandle] Expression methodCall) { return FromExpression(methodCall, null); } public static Job FromExpression([NotNull, InstantHandle] Expression methodCall, [CanBeNull] string queue) { return FromExpression(methodCall, null, queue); } /// /// Gets a new instance of the class based on the /// given expression tree of a method call. /// /// /// Expression tree of a method call. /// /// is null. /// /// expression body is not of type /// . /// /// expression contains a method that is not supported. /// /// instance object of a given expression points to . /// /// /// /// The property of a returning job will /// point to the type of a given instance object when it is specified, /// or to the declaring type otherwise. All the arguments are evaluated /// using the expression compiler that uses caching where possible to /// decrease the performance penalty. /// /// Instance object (e.g. () => instance.Method()) is /// only used to obtain the type for a job. It is not /// serialized and not passed across the process boundaries. /// public static Job FromExpression([NotNull, InstantHandle] Expression> methodCall) { return FromExpression(methodCall, null); } public static Job FromExpression([NotNull, InstantHandle] Expression> methodCall, [CanBeNull] string queue) { return FromExpression(methodCall, null, queue); } /// /// Gets a new instance of the class based on the /// given expression tree of an instance method call with explicit /// type specification. /// /// Explicit type that should be used on method call. /// Expression tree of a method call on . /// /// is null. /// /// expression body is not of type /// . /// /// expression contains a method that is not supported. /// /// /// All the arguments are evaluated using the expression compiler /// that uses caching where possible to decrease the performance /// penalty. /// public static Job FromExpression([NotNull, InstantHandle] Expression> methodCall) { return FromExpression(methodCall, null); } public static Job FromExpression([NotNull, InstantHandle] Expression> methodCall, [CanBeNull] string queue) { return FromExpression(methodCall, typeof(TType), queue); } /// /// Gets a new instance of the class based on the /// given expression tree of an instance method call with explicit /// type specification. /// /// Explicit type that should be used on method call. /// Expression tree of a method call on . /// /// is null. /// /// expression body is not of type /// . /// /// expression contains a method that is not supported. /// /// /// All the arguments are evaluated using the expression compiler /// that uses caching where possible to decrease the performance /// penalty. /// public static Job FromExpression([NotNull, InstantHandle] Expression> methodCall) { return FromExpression(methodCall, null); } public static Job FromExpression([NotNull, InstantHandle] Expression> methodCall, [CanBeNull] string queue) { return FromExpression(methodCall, typeof(TType), queue); } private static Job FromExpression([NotNull] LambdaExpression methodCall, [CanBeNull] Type explicitType, [CanBeNull] string queue) { if (methodCall == null) throw new ArgumentNullException(nameof(methodCall)); var callExpression = methodCall.Body as MethodCallExpression; if (callExpression == null) { throw new ArgumentException("Expression body should be of type `MethodCallExpression`", nameof(methodCall)); } var type = explicitType ?? callExpression.Method.DeclaringType; var method = callExpression.Method; if (explicitType == null && callExpression.Object != null) { // Creating a job that is based on a scope variable. We should infer its // type and method based on its value, and not from the expression tree. // TODO: BREAKING: Consider removing this special case entirely. // People consider that the whole object is serialized, this is not true. var objectValue = GetExpressionValue(callExpression.Object); if (objectValue == null) { throw new InvalidOperationException("Expression object should be not null."); } // TODO: BREAKING: Consider using `callExpression.Object.Type` expression instead. type = objectValue.GetType(); // If an expression tree is based on interface, we should use its own // MethodInfo instance, based on the same method name and parameter types. method = type.GetNonOpenMatchingMethod( callExpression.Method.Name, callExpression.Method.GetParameters().Select(static x => x.ParameterType).ToArray()); } return new Job( // ReSharper disable once AssignNullToNotNullAttribute type, method, GetExpressionValues(callExpression.Arguments), queue); } private static void Validate( Type type, [InvokerParameterName] string typeParameterName, MethodInfo method, // ReSharper disable once UnusedParameter.Local [InvokerParameterName] string methodParameterName, // ReSharper disable once UnusedParameter.Local int argumentCount, [InvokerParameterName] string argumentParameterName) { if (!method.IsPublic) { throw new NotSupportedException("Only public methods can be invoked in the background. Ensure your method has the `public` access modifier, and you aren't using explicit interface implementation."); } if (method.ContainsGenericParameters) { throw new NotSupportedException("Job method can not contain unassigned generic type parameters."); } if (method.DeclaringType == null) { throw new NotSupportedException("Global methods are not supported. Use class methods instead."); } if (!method.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new ArgumentException( $"The type `{method.DeclaringType}` must be derived from the `{type}` type.", typeParameterName); } if (method.ReturnType == typeof(void) && AsyncStateMachineAttributeCache.GetOrAdd(method, static m => m.GetCustomAttribute()) != null) { throw new NotSupportedException("Async void methods are not supported. Use async Task instead."); } var parameters = method.GetParameters(); if (parameters.Length != argumentCount) { throw new ArgumentException( "Argument count must be equal to method parameter count.", argumentParameterName); } foreach (var parameter in parameters) { // There is no guarantee that specified method will be invoked // in the same process. Therefore, output parameters and parameters // passed by reference are not supported. if (parameter.IsOut) { throw new NotSupportedException( "Output parameters are not supported: there is no guarantee that specified method will be invoked inside the same process."); } if (parameter.ParameterType.IsByRef) { throw new NotSupportedException( "Parameters, passed by reference, are not supported: there is no guarantee that specified method will be invoked inside the same process."); } var parameterTypeInfo = parameter.ParameterType.GetTypeInfo(); if (parameterTypeInfo.IsSubclassOf(typeof(Delegate)) || parameterTypeInfo.IsSubclassOf(typeof(Expression))) { throw new NotSupportedException( "Anonymous functions, delegates and lambda expressions aren't supported in job method parameters: it's very hard to serialize them and all their scope in general."); } } } private static object[] GetExpressionValues(ReadOnlyCollection expressions) { var result = expressions.Count > 0 ? new object[expressions.Count] : []; var index = 0; foreach (var expression in expressions) { result[index++] = GetExpressionValue(expression); } return result; } private static object GetExpressionValue(Expression expression) { var constantExpression = expression as ConstantExpression; return constantExpression != null ? constantExpression.Value : CachedExpressionCompiler.Evaluate(expression); } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Common { /// /// Represents a metadata class that contains a reference to the /// implementation of one or more of the filter interfaces, the filter's /// order, and the filter's scope. /// public class JobFilter { /// /// Represents a constant that is used to specify the default ordering of filters. /// public const int DefaultOrder = -1; /// /// Initializes a new instance of the Filter class. /// /// Filter instance. /// Filter scope. /// The run order. public JobFilter(object instance, JobFilterScope scope, int? order) { if (instance == null) { throw new ArgumentNullException(nameof(instance)); } if (order == null && instance is IJobFilter jobFilter) { order = jobFilter.Order; } Instance = instance; Order = order ?? DefaultOrder; Scope = scope; } /// /// Gets the instance of the filter. /// public object Instance { get; protected set; } /// /// Gets the order in which the filter is applied. /// public int Order { get; protected set; } /// /// Gets the scope ordering of the filter. /// public JobFilterScope Scope { get; protected set; } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilterAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.ComponentModel; using System.Linq; using System.Reflection; using Newtonsoft.Json; namespace Hangfire.Common { /// /// Represents the base class for job filter attributes. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)] public abstract class JobFilterAttribute : Attribute, IJobFilter { private static readonly ConcurrentDictionary MultiuseAttributeCache = new ConcurrentDictionary(); private int _order = JobFilter.DefaultOrder; [JsonIgnore] public bool AllowMultiple => AllowsMultiple(GetType()); [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] [DefaultValue(JobFilter.DefaultOrder)] public int Order { get { return _order; } set { if (value < JobFilter.DefaultOrder) { throw new ArgumentOutOfRangeException(nameof(value), "The Order value should be greater or equal to '-1'"); } _order = value; } } #if !NETSTANDARD1_3 [JsonIgnore] public override object TypeId => base.TypeId; #endif private static bool AllowsMultiple(Type attributeType) { return MultiuseAttributeCache.GetOrAdd( attributeType, static type => type.GetTypeInfo() .GetCustomAttributes(inherit: true) .First() .AllowMultiple); } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilterAttributeFilterProvider.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using System.Linq; namespace Hangfire.Common { /// /// Defines a filter provider for filter attributes. /// public class JobFilterAttributeFilterProvider : IJobFilterProvider { private readonly bool _cacheAttributeInstances; /// /// Initializes a new instance of the /// class with the attribute instance caching enabled. /// public JobFilterAttributeFilterProvider() : this(true) { } /// /// Initializes a new instance of the /// class and optionally caches attribute instances. /// /// public JobFilterAttributeFilterProvider(bool cacheAttributeInstances) { _cacheAttributeInstances = cacheAttributeInstances; } protected virtual IEnumerable GetTypeAttributes(Job job) { return job.GetTypeFilterAttributes(_cacheAttributeInstances); } protected virtual IEnumerable GetMethodAttributes(Job job) { return job.GetMethodFilterAttributes(_cacheAttributeInstances); } public virtual IEnumerable GetFilters(Job job) { if (job == null) return Enumerable.Empty(); var typeAttributes = GetTypeAttributes(job); var methodAttributes = GetMethodAttributes(job); List combined = null; foreach (var typeAttribute in typeAttributes) { (combined ??= []).Add(new JobFilter(typeAttribute, JobFilterScope.Type, null)); } foreach (var methodAttribute in methodAttributes) { (combined ??= []).Add(new JobFilter(methodAttribute, JobFilterScope.Method, null)); } return combined ?? Enumerable.Empty(); } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilterCollection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Hangfire.Client; using Hangfire.Server; using Hangfire.States; namespace Hangfire.Common { /// /// Represents a class that contains the job filters. /// /// /// Job filters run for every create, perform and state change /// of every job. All the instances in the filters collection /// should be thread-safe. /// /// You can register a filter using the /// registration endpoint. /// public class JobFilterCollection : IJobFilterProvider, IEnumerable { private readonly List _filters = new List(); /// /// Gets the number of filters in the global job filter collection. /// public int Count => _filters.Count; /// /// Adds the specified filter to the global filter collection. /// /// The filter instance. public void Add(object filter) { AddInternal(filter, order: null); } /// /// Adds the specified filter to the global filter collection /// using the specified filter run order. /// /// The filter instance. /// The run order. public void Add(object filter, int order) { AddInternal(filter, order); } private void AddInternal(object filter, int? order) { ValidateFilterInstance(filter); _filters.Add(new JobFilter(filter, JobFilterScope.Global, order)); } /// /// Removes all filters from the global filter collection. /// public void Clear() { _filters.Clear(); } /// /// Determines whether a filter is in the global filter collection. /// /// The filter instance. /// True if the global filter collection contains the filter, otherwise false. public bool Contains(object filter) { return _filters.Any(x => x.Instance == filter); } /// /// Removes all filters that match the specified filter. /// /// The filter instance. public void Remove(object filter) { _filters.RemoveAll(x => x.Instance == filter); } /// /// Remove all filters of the specified type with strict type matching. /// /// Type of filters to remove. public void Remove() { Remove(typeof(T)); } /// /// Remove all filters of the specified type with strict type matching. /// /// Type of filters to remove. public void Remove(Type type) { _filters.RemoveAll(x => type == x.Instance.GetType()); } public IEnumerator GetEnumerator() { return _filters.GetEnumerator(); } IEnumerable IJobFilterProvider.GetFilters(Job job) { return _filters; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } // ReSharper disable once UnusedParameter.Local private static void ValidateFilterInstance(object instance) { if (instance != null && !(instance is IClientFilter || instance is IServerFilter || instance is IClientExceptionFilter || instance is IServerExceptionFilter || instance is IApplyStateFilter || instance is IElectStateFilter)) { throw new InvalidOperationException("Unsupported filter instance"); } } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilterInfo.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using System.Linq; using Hangfire.Client; using Hangfire.Server; using Hangfire.States; namespace Hangfire.Common { /// /// Encapsulates information about the available job filters. /// internal readonly struct JobFilterInfo { /// /// Initializes a new instance of the class using the specified filters collection. /// /// The filters collection. public JobFilterInfo(IEnumerable filters) { var filtersList = filters as List ?? filters.ToList(); ClientFilters = new FilterCollection(filtersList); ServerFilters = new FilterCollection(filtersList); ElectStateFilters = new FilterCollection(filtersList); ApplyStateFilters = new FilterCollection(filtersList); ClientExceptionFiltersReversed = new ReversedFilterCollection(filtersList); ServerExceptionFiltersReversed = new ReversedFilterCollection(filtersList); } /// /// Gets all the client filters in the application. /// /// /// /// The client filters. /// public FilterCollection ClientFilters { get; } /// /// Gets all the server filters in the application. /// /// /// /// The server filters. /// public FilterCollection ServerFilters { get; } /// /// Gets all the stat changing filters in the application. /// /// /// /// The state changing filters. /// public FilterCollection ElectStateFilters { get; } /// /// Gets all the state changed filters in the application. /// /// /// /// The state changed filters. /// public FilterCollection ApplyStateFilters { get; } /// /// Gets all the client exception filters in the application. /// /// /// /// The client exception filters. /// public ReversedFilterCollection ClientExceptionFiltersReversed { get; } /// /// Gets all the server exception filters in the application. /// /// /// /// The server exception filters. /// public ReversedFilterCollection ServerExceptionFiltersReversed { get; } public readonly struct FilterCollection(List filters) { public Enumerator GetEnumerator() => new Enumerator(filters); public ref struct Enumerator(List filters) { private readonly List _filters = filters; private int _index = 0; private T _current = default; public bool MoveNext() { List localFilters = _filters; while (_index < localFilters.Count) { if (localFilters[_index++].Instance is T instance) { _current = instance; return true; } } return MoveNextRare(); } public T Current => _current; private bool MoveNextRare() { _index = _filters.Count + 1; _current = default; return false; } } } public readonly struct ReversedFilterCollection(List filters) { public ReversedEnumerator GetEnumerator() => new ReversedEnumerator(filters); public ref struct ReversedEnumerator(List filters) { private readonly List _filters = filters; private int _index = filters.Count - 1; private T _current = default; public bool MoveNext() { List localFilters = _filters; while (_index >= 0) { if (localFilters[_index--].Instance is T instance) { _current = instance; return true; } } return MoveNextRare(); } public T Current => _current; private bool MoveNextRare() { _index = -1; _current = default; return false; } } } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilterProviderCollection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Hangfire.Common { /// /// Represents the collection of filter providers for the application. /// public class JobFilterProviderCollection : Collection, IJobFilterProvider { private static readonly JobFilterComparer FilterComparer = new JobFilterComparer(); /// /// Initializes a new instance of the /// class. /// public JobFilterProviderCollection() { } public JobFilterProviderCollection(params IJobFilterProvider[] providers) : base(providers) { } /// /// Returns the collection of filter providers. /// /// Job, can be null. /// The collection of filter providers. public IEnumerable GetFilters(Job job) { var combinedFilters = new List(); for (var i = 0; i < Items.Count; i++) { combinedFilters.AddRange(Items[i].GetFilters(job)); } if (combinedFilters.Count > 1) { // Sorting before removing duplicates in the correct order combinedFilters = combinedFilters.OrderBy(static filter => filter, FilterComparer).ToList(); RemoveDuplicates(combinedFilters); } return combinedFilters; } private static void RemoveDuplicates(List filters) { var visitedTypes = new HashSet(); // Remove duplicates from the back forward for (var i = filters.Count - 1; i >= 0; i--) { var filterInstance = filters[i].Instance; if (!visitedTypes.Add(filterInstance.GetType()) && !AllowMultiple(filterInstance)) { filters.RemoveAt(i); } } } private static bool AllowMultiple(object filterInstance) { if (filterInstance is IJobFilter jobFilter) { return jobFilter.AllowMultiple; } return true; } private sealed class JobFilterComparer : IComparer { public int Compare(JobFilter x, JobFilter y) { // Nulls always have to be less than non-nulls if (x == null && y == null) { return 0; } if (x == null) { return -1; } if (y == null) { return 1; } // Sort first by order... if (x.Order < y.Order) { return -1; } if (x.Order > y.Order) { return 1; } // ...then by scope if (x.Scope < y.Scope) { return -1; } if (x.Scope > y.Scope) { return 1; } return 0; } } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilterProviders.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Common { /// /// Provides a registration point for filters. /// public static class JobFilterProviders { static JobFilterProviders() { // ReSharper disable once UseObjectOrCollectionInitializer Providers = new JobFilterProviderCollection(); Providers.Add(GlobalJobFilters.Filters); Providers.Add(new JobFilterAttributeFilterProvider()); } /// /// Provides a registration point for filters. /// public static JobFilterProviderCollection Providers { get; } } } ================================================ FILE: src/Hangfire.Core/Common/JobFilterScope.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Client; using Hangfire.Server; using Hangfire.States; namespace Hangfire.Common { /// /// Defines values that specify the order in which Hangfire filters /// run within the same filter type and filter order. /// /// /// /// Hangfire supports the following types of filters: /// /// /// /// /// Client / Server filters, which implement /// and /// interfaces respectively. /// /// /// /// /// State changing filters, which implement the /// interface. /// /// /// /// /// State changed filters, which implement the /// interface. /// /// /// /// /// Client / Server exception filters, which implement /// or /// interfaces /// respectively. /// /// /// /// /// Порядок запуска указанных типов фильтров строго фиксирован, например, /// фильтры исключений всегда выполняются после всех остальных фильтров, /// а фильтры состояний всегда запускаются внутри клиентских и серверных /// фильтров. /// /// Внутри же одного типа фильтров, порядок выполнения сначала определяется /// значением Order, а затем значением Scope. Перечисление /// определяет следующие значения (в порядке, в котором они будут выполнены): /// /// /// /// /// . /// /// /// /// /// . /// /// /// /// /// . /// /// /// /// /// Для примера, клиентский фильтр, у которого свойство Order имеет значение 0, /// а значение filter scope равно , /// будет выполнен раньше фильтра с тем же самым значением Order, /// но c filter scope, равным . /// /// Значения Scope задаются, в основном, в реализациях интерфейса /// . Так, класс /// определяет значение Scope как . /// /// Порядок выполнения фильтров одинакового типа, с одинаковым значением /// Order и с одинаковым scope, не оговаривается. /// public enum JobFilterScope { /// /// Specifies an order before the . /// Global = 10, /// /// Specifies an order after the and /// before the . /// Type = 20, /// /// Specifies an order after the . /// Method = 30, } } ================================================ FILE: src/Hangfire.Core/Common/JobHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Globalization; using Hangfire.Annotations; using Newtonsoft.Json; namespace Hangfire.Common { public static class JobHelper { [Obsolete("Please use `GlobalConfiguration.UseSerializerSettings` instead. Will be removed in 2.0.0")] public static void SetSerializerSettings(JsonSerializerSettings setting) { SerializationHelper.SetUserSerializerSettings(setting); } [Obsolete("Please use `SerializationHelper.Serialize` with appropriate serialization option instead. Will be removed in 2.0.0")] public static string ToJson(object value) { return SerializationHelper.Serialize(value, null, SerializationOption.User); } [Obsolete("Please use `SerializationHelper.Deserialize` with appropriate serialization option instead. Will be removed in 2.0.0")] public static T FromJson(string value) { return SerializationHelper.Deserialize(value, SerializationOption.User); } [Obsolete("Please use `SerializationHelper.Deserialize` with appropriate serialization option instead. Will be removed in 2.0.0")] public static object FromJson(string value, [NotNull] Type type) { return SerializationHelper.Deserialize(value, type, SerializationOption.User); } private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static readonly DateTime MillisecondTimestampBoundaryDate = new DateTime(1978, 1, 11, 21, 31, 40, 799, DateTimeKind.Utc); private static readonly long MillisecondTimestampBoundary = 253402300799L; public static long ToTimestamp(DateTime value) { TimeSpan elapsedTime = value - Epoch; return (long)elapsedTime.TotalSeconds; } public static DateTime FromTimestamp(long value) { return Epoch.AddSeconds(value); } public static long ToMillisecondTimestamp(DateTime value) { TimeSpan elapsedTime = value - Epoch; return (long)elapsedTime.TotalMilliseconds; } public static DateTime FromMillisecondTimestamp(long value) { return Epoch.AddMilliseconds(value); } public static string SerializeDateTime(DateTime value) { if (value > MillisecondTimestampBoundaryDate && value < DateTime.MaxValue && GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_170)) { return ToMillisecondTimestamp(value).ToString("D", CultureInfo.InvariantCulture); } return value.ToString("O", CultureInfo.InvariantCulture); } public static DateTime DeserializeDateTime(string value) { if (long.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out var timestamp)) { return timestamp > MillisecondTimestampBoundary ? FromMillisecondTimestamp(timestamp) : FromTimestamp(timestamp); } return DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); } public static DateTime? DeserializeNullableDateTime(string value) { if (String.IsNullOrEmpty(value)) { return null; } return DeserializeDateTime(value); } } } ================================================ FILE: src/Hangfire.Core/Common/JobLoadException.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; namespace Hangfire.Common { /// /// The exception that is thrown when a job could not /// be loaded from the storage due to missing or incorrect /// information about its type or method. /// #if !NETSTANDARD1_3 [Serializable] #endif public class JobLoadException : Exception { /// /// Initializes a new instance of the /// class with a given message and information about inner exception. /// public JobLoadException(string message, Exception inner) : base(message, inner) { } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected JobLoadException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif } } ================================================ FILE: src/Hangfire.Core/Common/LanguagePolyfills.cs ================================================ using System.ComponentModel; namespace System.Runtime.CompilerServices { #if !NET5_0_OR_GREATER [EditorBrowsable(EditorBrowsableState.Never)] internal static class IsExternalInit {} #endif // !NET5_0_OR_GREATER #if !NET7_0_OR_GREATER [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] internal sealed class RequiredMemberAttribute : Attribute {} [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public CompilerFeatureRequiredAttribute(string featureName) { FeatureName = featureName; } public string FeatureName { get; } public bool IsOptional { get; init; } public const string RefStructs = nameof(RefStructs); public const string RequiredMembers = nameof(RequiredMembers); } #endif // !NET7_0_OR_GREATER } namespace System.Diagnostics.CodeAnalysis { #if !NET7_0_OR_GREATER [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] internal sealed class SetsRequiredMembersAttribute : Attribute {} #endif } ================================================ FILE: src/Hangfire.Core/Common/MethodInfoExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Linq; using System.Reflection; namespace Hangfire.Common { internal static class MethodInfoExtensions { public static string GetNormalizedName(this MethodInfo methodInfo) { // Method names containing '.' are considered explicitly implemented interface methods // https://stackoverflow.com/a/17854048/1398672 return methodInfo.Name.Contains('.') && methodInfo.IsFinal && methodInfo.IsPrivate ? methodInfo.Name.Split('.').Last() : methodInfo.Name; } } } ================================================ FILE: src/Hangfire.Core/Common/ReflectedAttributeCache.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Reflection; namespace Hangfire.Common { internal static class ReflectedAttributeCache { private static readonly ConcurrentDictionary> TypeFilterAttributeCache = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> MethodFilterAttributeCache = new ConcurrentDictionary>(); public static ICollection GetTypeFilterAttributes(Type type) { return GetAttributes(TypeFilterAttributeCache, type.GetTypeInfo()); } public static ICollection GetMethodFilterAttributes(MethodInfo methodInfo) { return GetAttributes(MethodFilterAttributeCache, methodInfo); } private static ReadOnlyCollection GetAttributes( ConcurrentDictionary> lookup, TMemberInfo memberInfo) where TAttribute : Attribute where TMemberInfo : MemberInfo { Debug.Assert(memberInfo != null); Debug.Assert(lookup != null); return lookup.GetOrAdd(memberInfo, static mi => new ReadOnlyCollection(mi.GetCustomAttributes(inherit: true).ToArray())); } } } ================================================ FILE: src/Hangfire.Core/Common/SerializationHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.ExceptionServices; using System.Text; using System.Threading; using Hangfire.Annotations; using Newtonsoft.Json; namespace Hangfire.Common { public enum SerializationOption { /// /// For internal data using isolated settings that can't be changed from user code. /// Internal, /// /// For internal data using isolated settings with types information ( setting) /// that can't be changed from user code. /// TypedInternal, /// /// For user data like arguments and parameters, configurable via . /// User } /// /// Provides methods to serialize/deserialize data with Hangfire default settings. /// Isolates internal serialization process from user interference including `JsonConvert.DefaultSettings` modification. /// public static class SerializationHelper { private static readonly Lazy InternalSerializerSettings = new Lazy(GetInternalSettings, LazyThreadSafetyMode.PublicationOnly); private static JsonSerializerSettings _userSerializerSettings; /// /// Serializes data with option. /// Use this method to serialize internal data. Using isolated settings that can't be changed from user code. /// public static string Serialize([CanBeNull] T value) { return Serialize(value, SerializationOption.Internal); } /// /// Serializes data with specified option. /// Use option to serialize internal data. /// Use option if you need to store type information. /// Use option to serialize user data like arguments and parameters, /// configurable via . /// public static string Serialize([CanBeNull] T value, SerializationOption option) { return Serialize(value, typeof(T), option); } /// /// Serializes data with specified option. /// Use option to serialize internal data. /// Use option if you need to store type information. /// Use option to serialize user data like arguments and parameters, /// configurable via . /// public static string Serialize([CanBeNull] object value, [CanBeNull] Type type, SerializationOption option) { if (value == null) return null; if (GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_170)) { var serializerSettings = GetSerializerSettings(option); if (option == SerializationOption.User) { var formatting = serializerSettings?.Formatting ?? Formatting.None; return JsonConvert.SerializeObject(value, type, formatting, serializerSettings); } // For internal purposes we should ensure that JsonConvert.DefaultSettings don't affect // the serialization process, and the only way is to create a custom serializer. using var stringWriter = new StringWriter(new StringBuilder(256), CultureInfo.InvariantCulture); using (var jsonWriter = new JsonTextWriter(stringWriter)) { var serializer = JsonSerializer.Create(serializerSettings); serializer.Serialize(jsonWriter, value, type); } return stringWriter.ToString(); } else { // Previously almost all the data was serialized with the user settings, except // when we explicitly needed to persist the type information. In the latter case // custom settings passed to serializer, identical to TypedInternal. var serializerSettings = option == SerializationOption.TypedInternal ? GetLegacyTypedSerializerSettings() : GetUserSerializerSettings(); // JsonConvert is used here, because previously global default settings affected // the serialization process. var formatting = serializerSettings?.Formatting ?? Formatting.None; return JsonConvert.SerializeObject(value, type, formatting, serializerSettings); } } /// /// Deserializes data with option. /// Use this method to deserialize internal data. Using isolated settings that can't be changed from user code. /// public static object Deserialize([CanBeNull] string value, [NotNull] Type type) { return Deserialize(value, type, SerializationOption.Internal); } /// /// Deserializes data with specified option. /// Use to deserialize internal data. /// Use if deserializable internal data has type names information. /// Use to deserialize user data like arguments and parameters, /// configurable via . /// public static object Deserialize([CanBeNull] string value, [NotNull] Type type, SerializationOption option) { if (type == null) throw new ArgumentNullException(nameof(type)); if (value == null) return null; Exception exception = null; if (option != SerializationOption.User) { var serializerSettings = GetSerializerSettings(option); try { // For internal purposes we should ensure that JsonConvert.DefaultSettings don't affect // the deserialization process, and the only way is to create a custom serializer. using var stringReader = new StringReader(value); using var jsonReader = new JsonTextReader(stringReader); var serializer = JsonSerializer.Create(serializerSettings); return serializer.Deserialize(jsonReader, type); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // If there was an exception, we should try to deserialize the value using user-based // settings, because prior to 1.7.0 they were used for almost everything. So we are saving // the exception to re-throw it if even serializer based on user settings couldn't handle // our value. In that case an original exception should be thrown as it is the reason. exception = ex; } } try { return JsonConvert.DeserializeObject(value, type, GetSerializerSettings(SerializationOption.User)); } catch (Exception ex) when (exception != null && ex.IsCatchableExceptionType()) { ExceptionDispatchInfo.Capture(exception).Throw(); throw; } } /// /// Deserializes data with option. /// Use this method to deserialize internal data. Using isolated settings that can't be changed from user code. /// public static T Deserialize([CanBeNull] string value) { if (value == null) return default(T); return Deserialize(value, SerializationOption.Internal); } /// /// Deserializes data with specified option. /// Use to deserialize internal data. /// Use if deserializable internal data has type names information. /// Use to deserialize user data like arguments and parameters, /// configurable via . /// public static T Deserialize([CanBeNull] string value, SerializationOption option) { if (value == null) return default(T); return (T) Deserialize(value, typeof(T), option); } internal static JsonSerializerSettings GetInternalSettings() { var serializerSettings = new JsonSerializerSettings(); SetSimpleTypeNameAssemblyFormat(serializerSettings); serializerSettings.TypeNameHandling = TypeNameHandling.Auto; serializerSettings.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate; serializerSettings.NullValueHandling = NullValueHandling.Ignore; serializerSettings.CheckAdditionalContent = true; // Default option in JsonConvert.Deserialize method serializerSettings.MaxDepth = 128; #if NETSTANDARD2_0 serializerSettings.SerializationBinder = new TypeHelperSerializationBinder(); #else serializerSettings.Binder = new TypeHelperSerializationBinder(); #endif return serializerSettings; } internal static void SetUserSerializerSettings([CanBeNull] JsonSerializerSettings settings) { Volatile.Write(ref _userSerializerSettings, settings); } private static JsonSerializerSettings GetLegacyTypedSerializerSettings() { var serializerSettings = new JsonSerializerSettings(); serializerSettings.TypeNameHandling = TypeNameHandling.Objects; serializerSettings.MaxDepth = 128; SetSimpleTypeNameAssemblyFormat(serializerSettings); return serializerSettings; } private static void SetSimpleTypeNameAssemblyFormat(JsonSerializerSettings serializerSettings) { // Setting TypeNameAssemblyFormatHandling to Simple. Using reflection, because latest versions // of Newtonsoft.Json contain breaking changes. var typeNameAssemblyFormatHandling = typeof(JsonSerializerSettings).GetRuntimeProperty("TypeNameAssemblyFormatHandling"); var typeNameAssemblyFormat = typeof(JsonSerializerSettings).GetRuntimeProperty("TypeNameAssemblyFormat"); var property = typeNameAssemblyFormatHandling ?? typeNameAssemblyFormat; property.SetValue(serializerSettings, Enum.Parse(property.PropertyType, "Simple")); } private static JsonSerializerSettings GetSerializerSettings(SerializationOption serializationOption) { switch (serializationOption) { case SerializationOption.Internal: case SerializationOption.TypedInternal: return InternalSerializerSettings.Value; case SerializationOption.User: return GetUserSerializerSettings(); default: throw new ArgumentOutOfRangeException(nameof(serializationOption), serializationOption, null); } } private static JsonSerializerSettings GetUserSerializerSettings() { return Volatile.Read(ref _userSerializerSettings); } } } ================================================ FILE: src/Hangfire.Core/Common/ShallowExceptionHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.IO; using System.Text; using Hangfire.Annotations; namespace Hangfire.Common { internal static class ShallowExceptionHelper { private static readonly object DataKey = "OriginalStackTrace"; public static void PreserveOriginalStackTrace(this Exception exception) { if (exception != null && !exception.Data.Contains(DataKey)) { exception.Data.Add(DataKey, GetStackTrace(exception, includeFileInfo: false)); } } public static string ToStringWithOriginalStackTrace([NotNull] this Exception exception, int? numLines, bool includeFileInfo) { if (exception == null) throw new ArgumentNullException(nameof(exception)); return GetFirstLines(ToStringHelper(exception, false, includeFileInfo), numLines); } private static string ToStringHelper(Exception exception, bool isInner, bool includeFileInfo) { var sb = new StringBuilder(); sb.Append(exception.GetType().FullName); sb.Append(": "); sb.Append(exception.Message); if (exception.InnerException != null) { sb.Append(" ---> "); sb.Append(ToStringHelper(exception.InnerException, true, includeFileInfo)); } else sb.Append('\n'); var stackTrace = exception.Data.Contains(DataKey) ? (string)exception.Data[DataKey] : GetStackTrace(exception, includeFileInfo); if (!String.IsNullOrWhiteSpace(stackTrace)) { sb.Append(stackTrace); sb.Append('\n'); } if (isInner) sb.Append(" --- End of inner exception stack trace ---\n"); return sb.ToString(); } private static string GetFirstLines(string text, int? numLines) { if (text == null) return null; if (!numLines.HasValue || numLines.Value < 0) return text; using (var reader = new StringReader(text)) { var builder = new StringBuilder(); while (numLines-- > 0) { var line = reader.ReadLine(); if (line == null) break; if (builder.Length > 0) builder.AppendLine(); builder.Append(line); } return builder.ToString(); } } private static string GetStackTrace(Exception ex, bool includeFileInfo) { #if NETSTANDARD1_3 return ex.StackTrace; #else return new System.Diagnostics.StackTrace(ex, includeFileInfo).ToString(); #endif } } } ================================================ FILE: src/Hangfire.Core/Common/TypeExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using Hangfire.Annotations; namespace Hangfire.Common { internal static class TypeExtensions { private static readonly Regex GenericArgumentsRegex = new Regex(@"`[1-9]\d*", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); public static string ToGenericTypeString(this Type type) { if (!type.GetTypeInfo().IsGenericType) { return type.GetFullNameWithoutNamespace() .ReplacePlusWithDotInNestedTypeName(); } return type.GetGenericTypeDefinition() .GetFullNameWithoutNamespace() .ReplacePlusWithDotInNestedTypeName() .ReplaceGenericParametersInGenericTypeName(type); } public static MethodInfo GetNonOpenMatchingMethod( [NotNull] this Type type, [NotNull] string name, [CanBeNull] Type[] parameterTypes) { if (type == null) throw new ArgumentNullException(nameof(type)); if (name == null) throw new ArgumentNullException(nameof(name)); parameterTypes = parameterTypes ?? Type.EmptyTypes; var methodCandidates = new List(type.GetRuntimeMethods()); if (type.GetTypeInfo().IsInterface) { methodCandidates.AddRange(type.GetTypeInfo() .ImplementedInterfaces.SelectMany(static x => x.GetRuntimeMethods())); } foreach (var methodCandidate in methodCandidates) { if (!methodCandidate.GetNormalizedName().Equals(name, StringComparison.Ordinal)) { continue; } var parameters = methodCandidate.GetParameters(); if (parameters.Length != parameterTypes.Length) { continue; } var parameterTypesMatched = true; var genericArguments = methodCandidate.ContainsGenericParameters ? new Type[methodCandidate.GetGenericArguments().Length] : null; // Determining whether we can use this method candidate with // current parameter types. for (var i = 0; i < parameters.Length; i++) { var parameterType = parameters[i].ParameterType.GetTypeInfo(); var actualType = parameterTypes[i].GetTypeInfo(); if (!TypesMatchRecursive(parameterType, actualType, genericArguments)) { parameterTypesMatched = false; break; } } if (parameterTypesMatched) { if (genericArguments != null) { var genericArgumentsResolved = true; foreach (var genericArgument in genericArguments) { if (genericArgument == null) { genericArgumentsResolved = false; } } if (genericArgumentsResolved) { return methodCandidate.MakeGenericMethod(genericArguments); } } else { // Return first found method candidate with matching parameters. return methodCandidate; } } } return null; } public static Type[] GetAllGenericArguments(this TypeInfo type) { return type.GenericTypeArguments.Length > 0 ? type.GenericTypeArguments : type.GenericTypeParameters; } private static bool TypesMatchRecursive(TypeInfo parameterType, TypeInfo actualType, IList genericArguments) { if (parameterType.IsGenericParameter) { var position = parameterType.GenericParameterPosition; // Return false if this generic parameter has been identified and it's not the same as actual type if (genericArguments[position] != null && genericArguments[position].GetTypeInfo() != actualType) { return false; } genericArguments[position] = actualType.AsType(); return true; } if (parameterType.ContainsGenericParameters) { if (parameterType.IsArray) { // Return false if parameterType is array whereas actualType isn't if (!actualType.IsArray) return false; var parameterElementType = parameterType.GetElementType(); var actualElementType = actualType.GetElementType(); return TypesMatchRecursive(parameterElementType.GetTypeInfo(), actualElementType.GetTypeInfo(), genericArguments); } if (!actualType.IsGenericType || parameterType.GetGenericTypeDefinition() != actualType.GetGenericTypeDefinition()) { return false; } for (var i = 0; i < parameterType.GenericTypeArguments.Length; i++) { var parameterGenericArgument = parameterType.GenericTypeArguments[i]; var actualGenericArgument = actualType.GenericTypeArguments[i]; if (!TypesMatchRecursive(parameterGenericArgument.GetTypeInfo(), actualGenericArgument.GetTypeInfo(), genericArguments)) { return false; } } return true; } return parameterType != typeof(object).GetTypeInfo() ? parameterType.IsAssignableFrom(actualType) : parameterType == actualType; } private static string GetFullNameWithoutNamespace(this Type type) { if (type.IsGenericParameter) { return type.Name; } const int dotLength = 1; // ReSharper disable once PossibleNullReferenceException return !String.IsNullOrEmpty(type.Namespace) ? type.FullName.Substring(type.Namespace.Length + dotLength) : type.FullName; } private static string ReplacePlusWithDotInNestedTypeName(this string typeName) { return typeName.Replace('+', '.'); } private static string ReplaceGenericParametersInGenericTypeName(this string typeName, Type type) { var genericArguments = type .GetTypeInfo().GetAllGenericArguments(); typeName = GenericArgumentsRegex.Replace(typeName, match => { var currentGenericArgumentNumbers = int.Parse(match.Value.Substring(1), CultureInfo.InvariantCulture); var currentArguments = string.Join(",", genericArguments.Take(currentGenericArgumentNumbers).Select(ToGenericTypeString)); genericArguments = genericArguments.Skip(currentGenericArgumentNumbers).ToArray(); return string.Concat("<", currentArguments, ">"); }); return typeName; } } } ================================================ FILE: src/Hangfire.Core/Common/TypeHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; namespace Hangfire.Common { public class TypeHelper { private static readonly ConcurrentDictionary DefaultTypeSerializerCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary SimpleAssemblyTypeSerializerCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary DefaultTypeResolverCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary IgnoredAssemblyVersionTypeResolverCache = new ConcurrentDictionary(); private static readonly Assembly CoreLibrary = typeof(int).GetTypeInfo().Assembly; private static readonly AssemblyName MscorlibAssemblyName = new AssemblyName("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); private static readonly ConcurrentDictionary AssemblyCache = new ConcurrentDictionary(); private static readonly Regex VersionRegex = new Regex(@", Version=\d+.\d+.\d+.\d+", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); private static readonly Regex CultureRegex = new Regex(@", Culture=\w+", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); private static readonly Regex PublicKeyTokenRegex = new Regex(@", PublicKeyToken=\w+", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); private static Func _currentTypeResolver; private static Func _currentTypeSerializer; public static Func CurrentTypeResolver { get => Volatile.Read(ref _currentTypeResolver) ?? DefaultTypeResolver; set => Volatile.Write(ref _currentTypeResolver, value); } public static Func CurrentTypeSerializer { get => Volatile.Read(ref _currentTypeSerializer) ?? DefaultTypeSerializer; set => Volatile.Write(ref _currentTypeSerializer, value); } public static string DefaultTypeSerializer(Type type) { return DefaultTypeSerializerCache.GetOrAdd(type, static t => t.AssemblyQualifiedName); } public static string SimpleAssemblyTypeSerializer(Type type) { return SimpleAssemblyTypeSerializerCache.GetOrAdd(type, static value => { var builder = new StringBuilder(); SerializeType(value, true, builder); return builder.ToString(); }); } public static Type DefaultTypeResolver(string typeName) { return DefaultTypeResolverCache.GetOrAdd(typeName, static name => { #if NETSTANDARD1_3 name = name.Replace("System.Private.CoreLib", "mscorlib"); return Type.GetType( name, throwOnError: true); #else return Type.GetType( name, typeResolver: TypeResolver, assemblyResolver: CachedAssemblyResolver, throwOnError: true); #endif }); } public static Type IgnoredAssemblyVersionTypeResolver(string typeName) { return IgnoredAssemblyVersionTypeResolverCache.GetOrAdd(typeName, static value => { value = VersionRegex.Replace(value, String.Empty); value = CultureRegex.Replace(value, String.Empty); value = PublicKeyTokenRegex.Replace(value, String.Empty); return DefaultTypeResolver(value); }); } private static void SerializeType(Type type, bool withAssemblyName, StringBuilder typeNameBuilder) { if (type == typeof(System.Console)) { typeNameBuilder.Append("System.Console, mscorlib"); return; } if (type == typeof(System.Threading.Thread)) { typeNameBuilder.Append("System.Threading.Thread, mscorlib"); return; } if (type.IsArray && type.HasElementType) { var elementType = type.GetElementType()!; SerializeType(elementType, false, typeNameBuilder); typeNameBuilder.Append("[]"); } else { if (type.DeclaringType != null) { SerializeType(type.DeclaringType, false, typeNameBuilder); typeNameBuilder.Append('+'); } else if (type.Namespace != null) { typeNameBuilder.Append(type.Namespace).Append('.'); } typeNameBuilder.Append(type.Name); } if (type.GenericTypeArguments.Length > 0) { SerializeTypes(type.GenericTypeArguments, typeNameBuilder); } if (!withAssemblyName) return; var typeInfo = type.GetTypeInfo(); if (type != typeof(object) && type != typeof(string) && !typeInfo.IsPrimitive) { string assemblyName; var typeForwardedFrom = typeInfo.GetCustomAttribute(); if (typeForwardedFrom != null) { assemblyName = typeForwardedFrom.AssemblyFullName; var delimiterIndex = assemblyName.IndexOf(",", StringComparison.OrdinalIgnoreCase); assemblyName = delimiterIndex >= 0 ? assemblyName.Substring(0, delimiterIndex) : assemblyName; } else { assemblyName = typeInfo.Assembly.GetName().Name; } if (assemblyName.Equals("System.Private.CoreLib", StringComparison.OrdinalIgnoreCase)) { assemblyName = "mscorlib"; } typeNameBuilder.Append(", ").Append(assemblyName); } } private static void SerializeTypes(Type[] types, StringBuilder typeNamesBuilder) { if (types == null) return; typeNamesBuilder.Append('['); for (var i = 0; i < types.Length; i++) { typeNamesBuilder.Append('['); SerializeType(types[i], true, typeNamesBuilder); typeNamesBuilder.Append(']'); if (i != types.Length - 1) typeNamesBuilder.Append(','); } typeNamesBuilder.Append(']'); } private static Assembly CachedAssemblyResolver(AssemblyName assemblyName) { return AssemblyCache.GetOrAdd(assemblyName.FullName, AssemblyResolver); } private static Assembly AssemblyResolver(string assemblyString) { var assemblyName = new AssemblyName(assemblyString); if (assemblyName.Name.Equals("System.Console", StringComparison.OrdinalIgnoreCase) || assemblyName.Name.Equals("System.Private.CoreLib", StringComparison.OrdinalIgnoreCase) || assemblyName.Name.Equals("mscorlib", StringComparison.OrdinalIgnoreCase)) { assemblyName = MscorlibAssemblyName; } else if (assemblyName.Name.Equals("System.Private.Xml.Linq", StringComparison.OrdinalIgnoreCase)) { assemblyName = new AssemblyName("System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); } var publicKeyToken = assemblyName.GetPublicKeyToken(); #if !NETSTANDARD1_3 if (assemblyName.Version == null && assemblyName.CultureInfo == null && publicKeyToken == null) { #pragma warning disable 618 return Assembly.LoadWithPartialName(assemblyName.Name); #pragma warning restore 618 } #endif try { return Assembly.Load(assemblyName); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { var shortName = new AssemblyName(assemblyName.Name); if (publicKeyToken != null) { shortName.SetPublicKeyToken(publicKeyToken); } return Assembly.Load(shortName); } } private static Type TypeResolver(Assembly assembly, string typeName, bool ignoreCase) { if (typeName.Equals("System.Diagnostics.Debug", StringComparison.Ordinal)) { return typeof(System.Diagnostics.Debug); } assembly = assembly ?? CoreLibrary; if (assembly != CoreLibrary && assembly.GetName().Name.Equals("mscorlib", StringComparison.OrdinalIgnoreCase)) { // Everything defaults to `mscorlib` for interoperability reasons between // .NET Framework and .NET Core. Most of the types have the proper forwarding, // but newer types like DateOnly or TimeOnly don't. So for types from `mscorlib` // we perform the first search in the current core library. var type = CoreLibrary.GetType(typeName, false, ignoreCase); if (type != null) return type; } return assembly.GetType(typeName, true, ignoreCase); } } } ================================================ FILE: src/Hangfire.Core/Common/TypeHelperSerializationBinder.cs ================================================ // This file is part of Hangfire. Copyright © 2022 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; using Newtonsoft.Json; namespace Hangfire.Common { public sealed class TypeHelperSerializationBinder : SerializationBinder #if NETSTANDARD2_0 , Newtonsoft.Json.Serialization.ISerializationBinder #endif { public override Type BindToType(string assemblyName, string typeName) { return TypeHelper.CurrentTypeResolver($"{typeName}, {assemblyName}"); } public override void BindToName(Type serializedType, out string assemblyName, out string typeName) { assemblyName = null; typeName = TypeHelper.CurrentTypeSerializer(serializedType); if (typeName == null) return; var delimiterIndex = GetAssemblyNameDelimiterIndex(typeName); if (delimiterIndex >= 0) { assemblyName = typeName.Substring(delimiterIndex + 1).Trim(); typeName = typeName.Substring(0, delimiterIndex).Trim(); } } private static int GetAssemblyNameDelimiterIndex(string typeName) { var level = 0; for (var index = 0; index < typeName.Length; index++) { var current = typeName[index]; if (current == '[') level++; else if (current == ']') level--; else if (current == ',' && level == 0) return index; } return -1; } } } ================================================ FILE: src/Hangfire.Core/ContinuationsSupportAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Logging; using Hangfire.States; using Hangfire.Storage; namespace Hangfire { public class ContinuationsSupportAttribute : JobFilterAttribute, IElectStateFilter, IApplyStateFilter { internal static readonly HashSet KnownFinalStates = new HashSet { DeletedState.StateName, SucceededState.StateName }; private static readonly TimeSpan AddJobLockTimeout = TimeSpan.FromMinutes(1); private static readonly TimeSpan ContinuationStateFetchTimeout = TimeSpan.FromMinutes(1); private static readonly TimeSpan ContinuationInvalidTimeout = TimeSpan.FromMinutes(15); private readonly ILog _logger = LogProvider.For(); private readonly bool _pushResults; private readonly HashSet _knownFinalStates; private readonly IBackgroundJobStateChanger _stateChanger; public ContinuationsSupportAttribute() : this(false) { } public ContinuationsSupportAttribute(bool pushResults) : this(pushResults, KnownFinalStates) { } public ContinuationsSupportAttribute(HashSet knownFinalStates) : this(false, knownFinalStates) { } public ContinuationsSupportAttribute(bool pushResults, HashSet knownFinalStates) : this(pushResults, knownFinalStates, new BackgroundJobStateChanger()) { } public ContinuationsSupportAttribute( [NotNull] HashSet knownFinalStates, [NotNull] IBackgroundJobStateChanger stateChanger) : this(false, knownFinalStates, stateChanger) { } public ContinuationsSupportAttribute( bool pushResults, [NotNull] HashSet knownFinalStates, [NotNull] IBackgroundJobStateChanger stateChanger) { if (knownFinalStates == null) throw new ArgumentNullException(nameof(knownFinalStates)); if (stateChanger == null) throw new ArgumentNullException(nameof(stateChanger)); _pushResults = pushResults; _knownFinalStates = knownFinalStates; _stateChanger = stateChanger; // Ensure this filter is the last filter in the chain to start // continuations on the last candidate state only. Order = 1000; } public void OnStateElection(ElectStateContext context) { var awaitingState = context.CandidateState as AwaitingState; if (awaitingState != null) { // Branch for a child background job. AddContinuation(context, awaitingState); } else if (_knownFinalStates.Contains(context.CandidateState.Name)) { // Branch for a parent background job. ExecuteContinuationsIfExist(context); } } public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) { // TODO: Remove this method and IApplyStateFilter interface in 2.0.0. } internal static List DeserializeContinuations(string serialized) { var continuations = SerializationHelper.Deserialize>(serialized); if (continuations != null && continuations.TrueForAll(static x => x.JobId == null)) { continuations = SerializationHelper.Deserialize>(serialized, SerializationOption.User); } return continuations ?? new List(); } private void AddContinuation(ElectStateContext context, AwaitingState awaitingState) { var connection = context.Connection; var parentId = awaitingState.ParentId; // We store continuations as a json array in a job parameter. Since there // is no way to add a continuation in an atomic way, we are placing a // distributed lock on parent job to prevent race conditions, when // multiple threads add continuation to the same parent job. using (connection.AcquireDistributedJobLock(parentId, AddJobLockTimeout)) { var jobData = connection.GetJobData(parentId); if (jobData == null) { // When we try to add a continuation for a removed job, // the system should throw an exception instead of creating // corrupted state. throw new InvalidOperationException( $"Can not add a continuation: parent background job '{parentId}' does not exist."); } var continuations = GetContinuations(context, parentId); // Continuation may be already added. This may happen, when outer transaction // was failed after adding a continuation last time, since the addition is // performed outside of an outer transaction. if (!continuations.Exists(x => x.JobId == context.BackgroundJob.Id)) { continuations.Add(new Continuation { JobId = context.BackgroundJob.Id, Options = awaitingState.Options }); // Set continuation only after ensuring that parent job still // exists. Otherwise we could create add non-expiring (garbage) // parameter for the parent job. SetContinuations(connection, parentId, continuations); } var currentState = connection.GetStateData(parentId); if (currentState != null && _knownFinalStates.Contains(currentState.Name)) { var startImmediately = ShouldStartContinuation(currentState.Name, awaitingState.Options); if (_pushResults && startImmediately) { if (SucceededState.StateName.Equals(currentState.Name, StringComparison.OrdinalIgnoreCase)) { if (currentState.Data.TryGetValue("Result", out var antecedentResult)) { context.Connection.SetJobParameter(context.BackgroundJob.Id, "AntecedentResult", antecedentResult); } } else if (DeletedState.StateName.Equals(currentState.Name, StringComparison.OrdinalIgnoreCase)) { if (!currentState.Data.TryGetValue("Exception", out var antecedentException)) { antecedentException = JobParameterInjectionFilter.DefaultException; } context.Connection.SetJobParameter(context.BackgroundJob.Id, "AntecedentException", antecedentException); } } context.CandidateState = startImmediately ? awaitingState.NextState : new DeletedState { Reason = "Continuation condition was not met" }; } } } private void ExecuteContinuationsIfExist(ElectStateContext context) { // The following lines are executed inside a distributed job lock, // so it is safe to get continuation list here. var continuations = GetContinuations(context, null); var nextStates = new Dictionary(); // Getting continuation data for all continuations – state they are waiting // for and their next state. foreach (var continuation in continuations) { if (String.IsNullOrWhiteSpace(continuation.JobId)) continue; var currentState = GetContinuationState(context, continuation.JobId, ContinuationStateFetchTimeout); if (currentState == null) { continue; } // All continuations should be in the awaiting state. If someone changed // the state of a continuation, we should simply skip it. if (currentState.Name != AwaitingState.StateName) continue; IState nextState; if (!ShouldStartContinuation(context.CandidateState.Name, continuation.Options)) { nextState = new DeletedState { Reason = "Continuation condition was not met" }; } else { try { nextState = SerializationHelper.Deserialize(currentState.Data["NextState"], SerializationOption.TypedInternal); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { nextState = new FailedState(ex) { Reason = "An error occurred while deserializing the continuation" }; } } if (!nextStates.ContainsKey(continuation.JobId)) { // Duplicate continuations possible, when they were added before version 1.6.10. // Please see details in comments for the AddContinuation method near the line // with checking for existence (continuations.Exists). nextStates.Add(continuation.JobId, nextState); } } string antecedentResult = null; string antecedentException = null; if (_pushResults) { if (context.CandidateState is SucceededState) { var serializedData = context.CandidateState.SerializeData(); serializedData.TryGetValue("Result", out antecedentResult); } else if (context.CandidateState is DeletedState) { var serializedData = context.CandidateState.SerializeData(); if (!serializedData.TryGetValue("Exception", out antecedentException)) { antecedentException = JobParameterInjectionFilter.DefaultException; } } } foreach (var tuple in nextStates) { if (antecedentResult != null) { context.Connection.SetJobParameter(tuple.Key, "AntecedentResult", antecedentResult); } if (antecedentException != null) { context.Connection.SetJobParameter(tuple.Key, "AntecedentException", antecedentException); } _stateChanger.ChangeState(new StateChangeContext( context.Storage, context.Connection, tuple.Key, tuple.Value, AwaitingState.StateName)); } } private StateData GetContinuationState(ElectStateContext context, string continuationJobId, TimeSpan timeout) { StateData currentState = null; var started = Stopwatch.StartNew(); var firstAttempt = true; while (true) { var continuationData = context.Connection.GetJobData(continuationJobId); if (continuationData == null) { _logger.Warn( $"Can not start continuation '{continuationJobId}' for background job '{context.BackgroundJob.Id}': continuation does not exist."); break; } currentState = context.Connection.GetStateData(continuationJobId); if (currentState != null) { break; } if (DateTime.UtcNow - continuationData.CreatedAt > ContinuationInvalidTimeout) { _logger.Warn( $"Continuation '{continuationJobId}' has been ignored: it was deemed to be aborted, because its state is still non-initialized."); break; } if (started.Elapsed >= timeout) { _logger.Warn( $"Can not start continuation '{continuationJobId}' for background job '{context.BackgroundJob.Id}': timeout expired while trying to fetch continuation state."); break; } Thread.Sleep(firstAttempt ? 0 : 100); firstAttempt = false; } return currentState; } private bool ShouldStartContinuation(string antecedentStateName, JobContinuationOptions options) { if (options == JobContinuationOptions.OnAnyFinishedState) { return _knownFinalStates.Contains(antecedentStateName); } if (options.HasFlag(JobContinuationOptions.OnlyOnSucceededState) && SucceededState.StateName.Equals(antecedentStateName, StringComparison.OrdinalIgnoreCase)) { return true; } if (options.HasFlag(JobContinuationOptions.OnlyOnDeletedState) && DeletedState.StateName.Equals(antecedentStateName, StringComparison.OrdinalIgnoreCase)) { return true; } return false; } private static void SetContinuations( IStorageConnection connection, string jobId, List continuations) { connection.SetJobParameter(jobId, "Continuations", SerializationHelper.Serialize(continuations)); } private static List GetContinuations(ElectStateContext context, string jobId) { // We are altering continuation list only when its background job is locked, // and parameter snapshot is obtained only when background job is locked, so // it's safe to use cached list when possible. string serialized; if (String.IsNullOrEmpty(jobId) && context.BackgroundJob.ParametersSnapshot != null) { context.BackgroundJob.ParametersSnapshot.TryGetValue("Continuations", out serialized); } else { serialized = context.Connection.GetJobParameter(jobId ?? context.BackgroundJob.Id, "Continuations"); } return DeserializeContinuations(serialized); } void IApplyStateFilter.OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction) { } internal struct Continuation { public string JobId { get; set; } public JobContinuationOptions Options { get; set; } } } } ================================================ FILE: src/Hangfire.Core/Cron.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire { /// /// Helper class that provides common values for the cron expressions. /// public static class Cron { /// /// Returns cron expression that fires every minute. /// public static string Minutely() { return "* * * * *"; } /// /// Returns cron expression that fires every hour at the first minute. /// public static string Hourly() { return Hourly(minute: 0); } /// /// Returns cron expression that fires every hour at the specified minute. /// /// The minute in which the schedule will be activated (0-59). public static string Hourly(int minute) { return $"{minute} * * * *"; } /// /// Returns cron expression that fires every day at 00:00 UTC. /// public static string Daily() { return Daily(hour: 0); } /// /// Returns cron expression that fires every day at the first minute of /// the specified hour in UTC. /// /// The hour in which the schedule will be activated (0-23). public static string Daily(int hour) { return Daily(hour, minute: 0); } /// /// Returns cron expression that fires every day at the specified hour and minute /// in UTC. /// /// The hour in which the schedule will be activated (0-23). /// The minute in which the schedule will be activated (0-59). public static string Daily(int hour, int minute) { return $"{minute} {hour} * * *"; } /// /// Returns cron expression that fires every week at Monday, 00:00 UTC. /// public static string Weekly() { return Weekly(DayOfWeek.Monday); } /// /// Returns cron expression that fires every week at 00:00 UTC of the specified /// day of the week. /// /// The day of week in which the schedule will be activated. public static string Weekly(DayOfWeek dayOfWeek) { return Weekly(dayOfWeek, hour: 0); } /// /// Returns cron expression that fires every week at the first minute /// of the specified day of week and hour in UTC. /// /// The day of week in which the schedule will be activated. /// The hour in which the schedule will be activated (0-23). public static string Weekly(DayOfWeek dayOfWeek, int hour) { return Weekly(dayOfWeek, hour, minute: 0); } /// /// Returns cron expression that fires every week at the specified day /// of week, hour and minute in UTC. /// /// The day of week in which the schedule will be activated. /// The hour in which the schedule will be activated (0-23). /// The minute in which the schedule will be activated (0-59). public static string Weekly(DayOfWeek dayOfWeek, int hour, int minute) { return $"{minute} {hour} * * {(int) dayOfWeek}"; } /// /// Returns cron expression that fires every month at 00:00 UTC of the first /// day of month. /// public static string Monthly() { return Monthly(day: 1); } /// /// Returns cron expression that fires every month at 00:00 UTC of the specified /// day of month. /// /// The day of month in which the schedule will be activated (1-31). public static string Monthly(int day) { return Monthly(day, hour: 0); } /// /// Returns cron expression that fires every month at the first minute of the /// specified day of month and hour in UTC. /// /// The day of month in which the schedule will be activated (1-31). /// The hour in which the schedule will be activated (0-23). public static string Monthly(int day, int hour) { return Monthly(day, hour, minute: 0); } /// /// Returns cron expression that fires every month at the specified day of month, /// hour and minute in UTC. /// /// The day of month in which the schedule will be activated (1-31). /// The hour in which the schedule will be activated (0-23). /// The minute in which the schedule will be activated (0-59). public static string Monthly(int day, int hour, int minute) { return $"{minute} {hour} {day} * *"; } /// /// Returns cron expression that fires every year on Jan, 1st at 00:00 UTC. /// public static string Yearly() { return Yearly(month: 1); } /// /// Returns cron expression that fires every year in the first day at 00:00 UTC /// of the specified month. /// /// The month in which the schedule will be activated (1-12). public static string Yearly(int month) { return Yearly(month, day: 1); } /// /// Returns cron expression that fires every year at 00:00 UTC of the specified /// month and day of month. /// /// The month in which the schedule will be activated (1-12). /// The day of month in which the schedule will be activated (1-31). public static string Yearly(int month, int day) { return Yearly(month, day, hour: 0); } /// /// Returns cron expression that fires every year at the first minute of the /// specified month, day and hour in UTC. /// /// The month in which the schedule will be activated (1-12). /// The day of month in which the schedule will be activated (1-31). /// The hour in which the schedule will be activated (0-23). public static string Yearly(int month, int day, int hour) { return Yearly(month, day, hour, minute: 0); } /// /// Returns cron expression that fires every year at the specified month, day, /// hour and minute in UTC. /// /// The month in which the schedule will be activated (1-12). /// The day of month in which the schedule will be activated (1-31). /// The hour in which the schedule will be activated (0-23). /// The minute in which the schedule will be activated (0-59). public static string Yearly(int month, int day, int hour, int minute) { return $"{minute} {hour} {day} {month} *"; } /// /// Returns cron expression that never fires. Specifically 31st of February. /// public static string Never() { return Yearly(2, 31); } /// /// Returns cron expression that fires every minutes. /// /// /// Please note that only those intervals into which the number 60 is evenly divisible make sense /// in Cron expressions, such as 2, 5, 10, 15, 30, etc. Intervals such as 7, 13, 25 will not work /// correctly. /// /// The number of minutes to wait between every activation. public static string MinuteInterval(int interval) { return $"*/{interval} * * * *"; } /// /// Returns cron expression that fires every hours. /// /// /// Please note that only those intervals into which the number 24 is evenly divisible make sense /// in Cron expressions, such as 2, 4, 6, 8, 12, etc. Intervals such as 7, 13, 25 will not work /// correctly. /// /// The number of hours to wait between every activation. public static string HourInterval(int interval) { return $"0 */{interval} * * *"; } /// /// Returns cron expression that fires every days. /// /// /// Please note that only those intervals into which the number 30 is evenly divisible make sense /// in Cron expressions, such as 2, 5, 6, 10, 15, etc. Intervals such as 7, 13, 25 will not work /// correctly. However, even in this case the actual intervals may be longer in months with /// 31 or 28 days. /// /// The number of days to wait between every activation. public static string DayInterval(int interval) { return $"0 0 */{interval} * *"; } /// /// Returns cron expression that fires every months. /// /// /// Please note that only those intervals into which the number 12 is evenly divisible make sense /// in Cron expressions, such as 2, 3, 4, 6. Intervals such as 7, 13, 25 will not work correctly. /// /// The number of months to wait between every activation. public static string MonthInterval(int interval) { return $"0 0 1 */{interval} *"; } #if FEATURE_CRONDESCRIPTOR /// /// Converts a Cron expression string into a description. /// /// A Cron expression string. /// English description. [Obsolete("Please install `CronExpressionDescriptor` package manually and use it.")] public static string GetDescription(string cronExpression) { string[] expressionParts = cronExpression.Split(' '); if (expressionParts.Length != 5) { throw new InvalidCastException("Invalid Cron Expression"); } foreach (string expressionPart in expressionParts) { int num; if (!Int32.TryParse(expressionPart, out num) && expressionPart != "*") { throw new InvalidCastException("Invalid Cron Expression"); } } return CronExpressionDescriptor.ExpressionDescriptor.GetDescription(cronExpression); } #endif } } ================================================ FILE: src/Hangfire.Core/Dashboard/BatchCommandDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Net; using System.Threading.Tasks; namespace Hangfire.Dashboard { internal sealed class BatchCommandDispatcher : IDashboardDispatcher { private readonly Action _command; public BatchCommandDispatcher(Action command) { _command = command; } #if FEATURE_OWIN [Obsolete("Use the `BatchCommandDispatcher(Action, string)` instead. Will be removed in 2.0.0.")] public BatchCommandDispatcher(Action command) { _command = (context, jobId) => command(RequestDispatcherContext.FromDashboardContext(context), jobId); } #endif public async Task Dispatch(DashboardContext context) { if (context.IsReadOnly) { context.Response.StatusCode = 401; return; } var jobIds = await context.Request.GetFormValuesAsync("jobs[]").ConfigureAwait(false); if (jobIds.Count == 0) { context.Response.StatusCode = 422; return; } foreach (var jobId in jobIds) { _command(context, jobId); } context.Response.StatusCode = (int)HttpStatusCode.NoContent; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/CombinedResourceDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Hangfire.Annotations; namespace Hangfire.Dashboard { internal sealed class CombinedResourceDispatcher : EmbeddedResourceDispatcher { private readonly IEnumerable> _resources; public CombinedResourceDispatcher( [NotNull] string contentType, [NotNull] IEnumerable> resources) : base(contentType, null, null) { if (resources == null) throw new ArgumentNullException(nameof(resources)); _resources = resources; } protected override async Task WriteResponse(DashboardResponse response) { IEnumerable> copy; lock (_resources) { copy = _resources.ToArray(); } foreach (var resource in copy) { await WriteResource( response, resource.Item1, resource.Item2).ConfigureAwait(false); } } } } ================================================ FILE: src/Hangfire.Core/Dashboard/CommandDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Net; using System.Threading.Tasks; namespace Hangfire.Dashboard { internal sealed class CommandDispatcher : IDashboardDispatcher { private readonly Func _command; public CommandDispatcher(Func command) { _command = command; } #if FEATURE_OWIN [Obsolete("Use the `CommandDispatcher(Func)` ctor instead. Will be removed in 2.0.0.")] public CommandDispatcher(Func command) { _command = context => command(RequestDispatcherContext.FromDashboardContext(context)); } #endif public Task Dispatch(DashboardContext context) { if (context.IsReadOnly) { context.Response.StatusCode = 401; return Task.FromResult(false); } var request = context.Request; var response = context.Response; if (!"POST".Equals(request.Method, StringComparison.OrdinalIgnoreCase)) { response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; return Task.FromResult(false); } if (_command(context)) { response.StatusCode = (int)HttpStatusCode.NoContent; } else { response.StatusCode = 422; } return Task.FromResult(true); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Content/css/hangfire-dark.css ================================================ @media (prefers-color-scheme: dark) { html { color-scheme: dark; } /* Common */ .page-header { border-bottom: 1px solid #4d4d4d; } body { background-color: #22272e; color: #adbac7; } a { color: #539bf5; } a:focus, a:hover { color: #539bf5; text-decoration: underline; } /* Tables */ .table .table { background-color: transparent; } .table-striped > tbody > tr:nth-of-type(odd) { background-color: #2d333b; } .table > tbody > tr.hover:hover > td, .table > tbody > tr.hover:hover > th { background-color: #2d333b; } .table > tbody > tr.highlight > td, .table > tbody > tr.highlight > th { background-color: #2d333b; border-color: #4d4d4d } .table > tbody > tr.highlight:hover > td, .table > tbody > tr.highlight:hover > th { background-color: #2d333b; border-color: #4d4d4d } .table > tbody > tr > td, .table > tbody > tr > th, .table > tfoot > tr > td, .table > tfoot > tr > th, .table > thead > tr > td, .table > thead > tr > th { border-color: #4d4d4d } /* Footer */ div#footer { background: #2d333b; } /* State cards */ .state-card { background-color: transparent; border: 1px solid #4d4d4d; padding: 0; } .state-card-body { background-color: transparent; border-top: 1px solid #4d4d4d; padding: 12px; margin: 0; } .state-card-title { background-color: transparent; padding: 12px; } .state-card-text { padding: 0 12px 12px 12px; margin: 0; } /* State card success */ .state-card-state-success { border-color: rgba(87, 171, 90, 0.4) !important; } .state-card-state-success .state-card-title { background-color: rgba(87, 171, 90, 0.1); border-color: rgba(87, 171, 90, 0.4); color: rgba(87, 171, 90) !important; } .state-card-state-success .state-card-body { background-color: transparent !important; border-top-color: rgba(87, 171, 90, 0.4); } .state-card-state-success .state-card-text { background-color: rgba(87, 171, 90, 0.1); } /* State card danger */ .state-card-state-danger { border-color: rgba(235, 156, 166, 0.3) !important; } .state-card-state-danger .state-card-title { background-color: rgba(215, 58, 74, 0.1); border-color: rgba(235, 156, 166, 0.3) !important; color: rgb(235, 156, 166) !important; } .state-card-state-danger .state-card-body { background-color: transparent !important; border-color: rgba(235, 156, 166, 0.3); } .state-card-state-danger .state-card-text { background-color: rgba(215, 58, 74, 0.1); } /* State card warning */ .state-card-state-warning { border-color: rgba(250, 201, 5, 0.3) !important; } .state-card-state-warning .state-card-title { background-color: rgba(251, 202, 4, 0.1); border-color: rgba(250, 201, 5, 0.3) !important; color: rgb(250, 201, 5) !important; } .state-card-state-warning .state-card-body { background-color: transparent !important; border-color: rgba(250, 201, 5, 0.3); } .state-card-state-warning .state-card-text { background-color: rgba(251, 202, 4, 0.1); } /* State card info */ .state-card-state-info { border-color: rgba(104, 167, 234, 0.3) !important; } .state-card-state-info .state-card-title { background-color: rgba(29, 118, 219, 0.1); border-color: rgba(104, 167, 234, 0.3) !important; color: rgb(104, 167, 234) !important; } .state-card-state-info .state-card-body { background-color: transparent !important; border-color: rgba(104, 167, 234, 0.3) } .state-card-state-info .state-card-text { background-color: rgba(29, 118, 219, 0.1); } /* State card inactive */ .state-card-state-inactive { border-color: #777 !important; } .state-card-state-inactive .state-card-title { color: #777 !important; } .state-card-state-inactive .state-card-body { background-color: #333 !important; } /* State card active */ .state-card-state-active { border-color: #999 !important; } .state-card-state-active .state-card-title { color: #999 !important; } .state-card-state-active .state-card-body { background-color: #999 !important; color: #444 !important; } /* Stats */ #stats { background: #2d333b; } #stats .list-group-item { border-color: #4d4d4d; background-color: transparent; } #stats a.list-group-item { color: rgba(255, 255, 255, 0.6); border-color: #4d4d4d; } #stats a.list-group-item:hover, #stats a.list-group-item:focus { border-color: #4d4d4d; color: #fff; } #stats .list-group-item.active, #stats .list-group-item.active:hover, #stats .list-group-item.active:focus { background: #373e47; border-color: #4d4d4d; color: #fff; } /* Navbar */ .navbar-default { background: #2d333b; border: none; } .navbar-default .navbar-brand { color: rgba(255, 255, 255, 0.6); } .navbar-default .navbar-brand:hover { color: rgba(255, 255, 255, 1); } .navbar-default .navbar-nav > li > a { color: rgba(255, 255, 255, 0.6); } .navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover { color: #ffffff; } .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .active > a:hover { background: #373e47; color: #ffffff; } .navbar-default .navbar-toggle { border-color: #4d4d4d; } .navbar-default .navbar-collapse, .navbar-default .navbar-form { border-color: #4d4d4d; } .navbar-default .navbar-toggle:focus, .navbar-default .navbar-toggle:hover { background-color: #373e47; } /* Paginator */ .paginator .btn { background: #2d333b; color: rgba(255, 255, 255, 0.6); } .paginator .btn.active, .paginator a.btn.btn-default.active { background: rgba(29, 118, 219, 0.6); color: #fff; } .btn-group .btn.active, .btn-group .paginator a.btn.btn-default.active { background: rgba(29, 118, 219, 0.6); color: #fff; } /* btn-default */ .btn-default { background: #2d333b; border-color: #4d4d4d; color: rgba(255, 255, 255, 0.9); } .btn-default.active.focus, .btn-default.active:focus, .btn-default.active:hover, .btn-default:active.focus, .btn-default:active:focus, .btn-default:active:hover, .open>.dropdown-toggle.btn-default.focus, .open>.dropdown-toggle.btn-default:focus, .open>.dropdown-toggle.btn-default:hover, .btn-default:hover, .btn-default:focus, .btn-default:active:focus, a.btn.btn-default.active { background: #373e47; border-color: #4d4d4d; color: rgba(255, 255, 255, 1); } .btn-default.disabled.focus, .btn-default.disabled:focus, .btn-default.disabled:hover, .btn-default[disabled].focus, .btn-default[disabled]:focus, .btn-default[disabled]:hover, fieldset[disabled] .btn-default.focus, fieldset[disabled] .btn-default:focus, fieldset[disabled] .btn-default:hover { background: #2d333b; border-color: #4d4d4d; color: rgba(255, 255, 255, 0.9); } /* btn-primary */ .btn-primary { background-color: rgba(29, 118, 219, 0.6); border: 1px solid rgba(29, 118, 219, 0.3); color: rgba(255, 255, 255, 0.9); } .btn-primary.active.focus, .btn-primary.active:focus, .btn-primary.active:hover, .btn-primary:active.focus, .btn-primary:active:focus, .btn-primary:active:hover, .open>.dropdown-toggle.btn-primary.focus, .open>.dropdown-toggle.btn-primary:focus, .open>.dropdown-toggle.btn-primary:hover, .btn-primary:hover, .btn-primary:focus, .btn-primary:active:focus, a.btn.btn-primary.active { background-color: rgba(29, 118, 219, 0.8); border: 1px solid rgba(29, 118, 219, 0.3); color: rgba(255, 255, 255, 1); } .btn-primary.disabled.focus, .btn-primary.disabled:focus, .btn-primary.disabled:hover, .btn-primary[disabled].focus, .btn-primary[disabled]:focus, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary.focus, fieldset[disabled] .btn-primary:focus, fieldset[disabled] .btn-primary:hover { background-color: rgba(29, 118, 219, 0.6); border: 1px solid rgba(29, 118, 219, 0.3); color: rgba(255, 255, 255, 0.9); } /* btn-death */ .btn-death { background-color: rgba(229, 88, 79, 0.6); border: 1px solid rgba(229, 88, 79, 0.3); color: rgba(255, 255, 255, 0.9); } .btn-death.active.focus, .btn-death.active:focus, .btn-death.active:hover, .btn-death:active.focus, .btn-death:active:focus, .btn-death:active:hover, .open>.dropdown-toggle.btn-death.focus, .open>.dropdown-toggle.btn-death:focus, .open>.dropdown-toggle.btn-death:hover, .btn-death:hover, .btn-death:focus, .btn-death:active:focus, a.btn.btn-death.active { background-color: rgba(229, 88, 79, 0.8); border: 1px solid rgba(229, 88, 79, 0.3); color: rgba(255, 255, 255, 1); } .btn-death.disabled.focus, .btn-death.disabled:focus, .btn-death.disabled:hover, .btn-death[disabled].focus, .btn-death[disabled]:focus, .btn-death[disabled]:hover, fieldset[disabled] .btn-death.focus, fieldset[disabled] .btn-death:focus, fieldset[disabled] .btn-death:hover { background-color: rgba(229, 88, 79, 0.6); border: 1px solid rgba(229, 88, 79, 0.3); color: rgba(255, 255, 255, 0.9); } /* btn-group */ /* Alerts */ .alert-info { background-color: rgba(29, 118, 219, 0.1); border-color: rgba(104, 167, 234, 0.3); color: rgb(104, 167, 234); } .alert-warning { background-color: rgba(251, 202, 4, 0.1); border-color: rgba(250, 201, 5, 0.3); color: rgb(250, 201, 5); } .alert-success { background-color: rgba(87, 171, 90, 0.1); border-color: rgba(87, 171, 90, 0.4); color: rgba(87, 171, 90); } .alert-danger { background-color: rgba(215, 58, 74, 0.1); border-color: rgba(235, 156, 166, 0.3); color: rgb(235, 156, 166); } /* Job Snippet */ .job-snippet { background: #22272e; border: 1px solid #4d4d4d; } .job-snippet-code code { color: #fff; } .job-snippet-code pre .comment { color: #57ab5a; } .job-snippet-code pre .keyword { color: #539bf5; } .job-snippet-code pre .string { color: #e5534b; } .job-snippet-code pre .type { color: rgb(43, 145, 175); } .job-snippet-code pre .xmldoc { color: rgb(128, 128, 128); } .job-snippet-properties code { color: #e5534b; } /* code */ pre code { border: none; } code { background-color: rgba(29, 118, 219, 0.1); border: 1px solid rgba(104, 167, 234, 0.3); color: rgb(104, 167, 234); } /* Stack Trace */ .stack-trace { background: transparent; border: 1px solid #4d4d4d; color: #fff; } .st-type { font-weight: bold; } .st-param-name { color: #666; } .st-file { color: #999; } .st-method { color: #539bf5; font-weight: bold; } .st-line { color: #bb00bb; } /* Metric */ div.metric.metric-default { border-color: #ddd; } div.metric-info, span.metric-info { color: #5bc0de; border-color: #5bc0de; } span.metric-info.highlighted { background-color: #5bc0de; } div.metric-warning, span.metric-warning { color: #f0ad4e; border-color: #f0ad4e; } span.metric-warning.highlighted { background-color: #f0ad4e; } div.metric-success, span.metric-success { color: #5cb85c; border-color: #5cb85c; } span.metric-success.highlighted { background-color: #5cb85c; } div.metric-danger, span.metric-danger { color: #d9534f; border-color: #d9534f; } span.metric-danger.highlighted { background-color: #d9534f; } /* State labels */ .label-state-success { background-color: rgba(87, 171, 90, 0.1) !important; border: 1px solid rgba(87, 171, 90, 0.4) !important; color: rgba(87, 171, 90) !important; } a:hover .label-hover.label-state-success { background-color: rgba(87, 171, 90, 0.4) !important; color: rgba(87, 171, 90) !important; } .label-state-danger { background-color: rgba(215, 58, 74, 0.1) !important; border: 1px solid rgba(235, 156, 166, 0.3) !important; color: rgb(235, 156, 166) !important; } a:hover .label-hover.label-state-danger { background-color: rgba(215, 58, 74, 0.4) !important; color: rgb(235, 156, 166) !important; } .label-state-warning { background-color: rgba(251, 202, 4, 0.1) !important; border: 1px solid rgba(250, 201, 5, 0.3) !important; color: rgb(250, 201, 5) !important; } a:hover .label-hover.label-state-warning { background-color: rgba(251, 202, 4, 0.4) !important; color: rgb(250, 201, 5) !important; } .label-state-info { background-color: rgba(29, 118, 219, 0.1) !important; border: 1px solid rgba(104, 167, 234, 0.3) !important; color: rgb(104, 167, 234) !important; } a:hover .label-hover.label-state-info { background-color: rgba(29, 118, 219, 0.4) !important; color: rgb(104, 167, 234) !important; } .label-state-inactive { background-color: rgba(204, 204, 204, 0.1) !important; border: 1px solid rgba(204, 204, 204, 0.3) !important; color: rgb(204, 204, 204) !important; } a:hover .label-hover.label-state-inactive { background-color: rgba(204, 204, 204, 0.4) !important; color: rgb(204, 204, 204) !important; } .label-state-active { background-color: rgba(204, 204, 204, 0.1); border: 1px solid rgba(204, 204, 204, 0.3) !important; color: rgb(204, 204, 204) !important; } a:hover .label-hover.label-state-active { background-color: rgba(204, 204, 204, 0.4) !important; color: rgb(204, 204, 204) !important; } /* Common */ .table td.failed-job-details { background-color: transparent; } .nav-tabs > li > a { color: rgba(255, 255, 255, 0.6); } .nav-tabs > li > a:hover { color: white; background-color: transparent; border-color: transparent!important; } .nav-tabs > li.active > a, .nav-tabs > li.active > a:focus, .nav-tabs > li.active > a:hover { color: white; background-color: #373e47; border-color: #4d4d4d #4d4d4d transparent #4d4d4d !important; } .table-hover > tbody > tr:hover { background-color: #2d333b; } .progress { background-color: #2d333b; } div.metric.metric-default { border-color: #4d4d4d; } @media (min-width: 768px) { .nav-tabs > li > a { border-bottom-color: #4d4d4d!important; } .nav-tabs > li > a:hover { border-bottom-color: #4d4d4d!important; } } @media screen and (max-width: 767px) { .table-responsive { border-color: #4d4d4d; } } .table-vertical tbody > tr > th { border-right-color: #4d4d4d!important; } } ================================================ FILE: src/Hangfire.Core/Dashboard/Content/css/hangfire.css ================================================ /* Sticky footer styles -------------------------------------------------- */ html, body { height: 100%; /* The html and body elements cannot have any padding or margin. */ } body { /* 75px to make the container go all the way to the bottom of the topbar */ padding-top: 75px; } .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 { min-height: 2px !important; } /* Wrapper for page content to push down footer */ #wrap { min-height: 100%; height: auto !important; height: 100%; /* Negative indent footer by its height */ margin: 0 auto -60px; /* Pad bottom by footer height */ padding: 0 0 60px; } /* Set the fixed height of the footer here */ #footer { background-color: #f5f5f5; } /* Custom page CSS -------------------------------------------------- */ .container .credit { margin: 20px 0; word-break: break-word; } .page-header { margin-top: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .btn-death { background-color: #777; border-color: #666; color: #fff; } .btn-death:hover { background-color: #666; border-color: #555; color: #fff; } .list-group .list-group-item .glyphicon { margin-right: 3px; } .breadcrumb { margin-bottom: 10px; background-color: inherit; padding: 0; } .btn-toolbar-label { padding: 7px 0; vertical-align: middle; display: inline-block; margin-left: 5px; } .btn-toolbar-label-sm { padding: 5px 0; } .btn-toolbar-spacer { width: 5px; display: inline-block; height: 1px; } .tooltip { word-break: break-word; } a:hover .label-hover { background-color: #2a6496!important; color: #fff!important; } .expander { cursor: pointer; } .expandable { display: none; } .table-inner { margin-bottom: 7px; font-size: 90%; } .min-width { width: 1%; white-space: nowrap; } .min-width-125p { min-width: 125px; } .min-width-200p { min-width: 200px; } .width-15 { width: 15%; } .width-20 { width: 20%; } .width-30 { width: 30%; } .glyphicon-sm { font-size: 10px; } @media (max-width: 991px) { .width-30 { width: 15%; } } .align-right { text-align: right; } .table>tbody>tr.hover:hover>td, .table>tbody>tr.hover:hover>th { background-color: #f9f9f9; } .table>tbody>tr.highlight>td, .table>tbody>tr.highlight>th { background-color: #fcf8e3; border-color: #fbeed5; } .table>tbody>tr.highlight:hover>td, .table>tbody>tr.highlight:hover>th { background-color: #f6f2dd; border-color: #f5e8ce; } .word-break { word-break: break-all; } /* Statistics widget -------------------------------------------------- */ #stats .list-group-item { border-color: #e7e7e7; background-color: #f8f8f8; } #stats a.list-group-item { color: #777; } #stats a.list-group-item:hover, #stats a.list-group-item:focus { color: #333; } #stats .list-group-item.active, #stats .list-group-item.active:hover, #stats .list-group-item.active:focus { color: #555; background-color: #e7e7e7; border-color: #e7e7e7; } .table td.failed-job-details { padding-top: 0; padding-bottom: 0; border-top: none; background-color: #f5f5f5; } .obsolete-data, .obsolete-data a, .obsolete-data pre, .obsolete-data .label { color: #999; } .obsolete-data pre, .obsolete-data .label { background-color: #999; color: #fff; } .obsolete-data .glyphicon-question-sign { font-size: 80%; color: #999; } .stack-trace { padding: 10px; border: none; } .st-type { font-weight: bold; } .st-param-name { color: #666; } .st-file { color: #999; } .st-method { color: #00008B; font-weight: bold; } .st-line { color: #8B008B; } .width-200 { width: 200px; } .btn-toolbar-top { margin-bottom: 10px; } .paginator .btn { color: #428bca; } .paginator .btn.active { color: #333; } /* Job Snippet styles */ .job-snippet { margin-bottom: 20px; padding: 15px; display: table; width: 100%; -ms-border-radius: 4px; border-radius: 4px; background-color: #f5f5f5; } .job-snippet > * { display: table-cell; vertical-align: top; } .job-snippet-code { vertical-align: top; } .job-snippet-code pre { border: none; margin: 0; background: inherit; padding: 0; -ms-border-radius: 0; border-radius: 0; font-size: 14px; } .job-snippet-code code { display: block; color: black; } .job-snippet-code pre .comment { color: rgb(0, 128, 0); } .job-snippet-code pre .keyword { color: rgb(0, 0, 255); } .job-snippet-code pre .string { color: rgb(163, 21, 21); } .job-snippet-code pre .type { color: rgb(43, 145, 175); } .job-snippet-code pre .xmldoc { color: rgb(128, 128, 128); } .job-snippet-properties pre { background-color: inherit; -webkit-box-shadow: none; -ms-box-shadow: none; padding: 2px 4px; border: none; margin: 0; overflow: hidden; } .job-snippet-properties code { color: rgb(163, 21, 21); } .alert-warning .table td { border-color: rgb(229, 220, 195); } .alert-fixed { border-radius: 0; margin-bottom: -1px; padding-left: 0; padding-right: 0; border-left: none; border-right: none; } .state-card { position: relative; display: block; margin-bottom: 7px; padding: 12px; background-color: #fff; border: 1px solid #e5e5e5; border-radius: 3px; } .state-card-title { margin-bottom: 0; } .state-card-title .pull-right { margin-top: 3px; } .state-card-text { margin-top: 5px; margin-bottom: 0; } .state-card h4 { margin-top: 0; } .state-card-body { padding: 10px; margin: 10px -12px -12px -12px; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; background-color: #f5f5f5; } .state-card-body dl { margin-top: 5px; margin-bottom: 0; } .state-card-body dd { word-break: break-all; } .state-card-body pre { white-space: pre-wrap; /* CSS 3 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ background: transparent; padding: 0; } .state-card-body .stack-trace { background-color: transparent; padding: 0 20px; margin-bottom: 0px; } .state-card-body .exception-type { margin-top: 0; } .state-card-state-active { border-color: #999; } .state-card-state-active .state-card-title { color: #999; } .state-card-state-active .state-card-body { background-color: #F5F5F5; } .state-card-state-success { border-color: #5cb85c; } .state-card-state-success .state-card-title { color: #5cb85c; } .state-card-state-success .state-card-body { background-color: #EDF7ED; } .state-card-state-danger { border-color: #d9534f; } .state-card-state-danger .state-card-title { color: #d9534f; } .state-card-state-danger .state-card-body { background-color: #FAEBEA; } .state-card-state-warning { border-color: #f0ad4e; } .state-card-state-warning .state-card-title { color: #f0ad4e; } .state-card-state-warning .state-card-body { background-color: #FCEFDC; } .state-card-state-info { border-color: #5bc0de; } .state-card-state-info .state-card-title { color: #5bc0de; } .state-card-state-info .state-card-body { background-color: #E0F3F8; } .state-card-state-inactive { border-color: #777; } .state-card-state-inactive .state-card-title { color: #777; } .state-card-state-inactive .state-card-body { background-color: #ddd; } .label-state-active { background-color: #999; } .label-state-success { background-color: #5cb85c; } .label-state-danger { background-color: #d9534f; } .label-state-warning { background-color: #f0ad4e; } .label-state-info { background-color: #5bc0de; } .label-state-inactive { background-color: #777; } /* Job History styles */ .job-history { margin-bottom: 10px; opacity: 0.8; } .job-history.job-history-current { opacity: 1.0; } .job-history-heading { padding: 5px 10px; color: #666; -ms-border-top-left-radius: 4px; border-top-left-radius: 4px; -ms-border-top-right-radius: 4px; border-top-right-radius: 4px; } .job-history-body { background-color: #f5f5f5; padding: 10px; } .job-history-title { margin-top: 0; margin-bottom: 2px; } .job-history dl { margin-top: 5px; margin-bottom: 5px; } .job-history .stack-trace { background-color: transparent; padding: 0 20px; margin-bottom: 5px; } .job-history .exception-type { margin-top: 0; } .job-history-current .job-history-heading, .job-history-current small { color: white; } a.job-method { color: inherit; } .list-group .glyphicon { top: 2px; } .text-decoration-none { text-decoration: none; } .display-none { display: none; } .display-block { display: block; } .margin-top-2p { margin-top: 2px; } .margin-bottom-0 { margin-bottom: 0; } .margin-bottom-20p { margin-bottom: 20px; } .margin-right-14p { margin-right: 14px; } span.metric { display: inline-block; min-width: 10px; padding: 2px 6px; font-size: 12px; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; background-color: transparent; border-radius: 10px; border: solid 1px; -webkit-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; -moz-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; -ms-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; -o-transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; } span.metric.highlighted { font-weight: bold; color: #fff!important; } span.metric-default { color: #777; border-color: #777; } span.metric-default.highlighted { background-color: #777; } .clickable-metric, .clickable-metric:hover, .clickable-metric:active, .clickable-metric:visited, .clickable-metric:focus { color: inherit; text-decoration: none; } div.metric { border: solid 1px transparent; border-radius: 4px; -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05); box-shadow: 0 1px 1px rgba(0,0,0,.05); margin-bottom: 20px; transition: color .1s ease-out, background .1s ease-out, border .1s ease-out; } div.metric .metric-body { padding: 15px 15px 0; font-size: 26px; text-align: center; overflow: hidden; text-overflow: ellipsis; } div.metric .metric-description { padding: 0 15px 15px; text-align: center; } div.metric.metric-default { border-color: #ddd; } div.metric-info, span.metric-info { color: #5bc0de; border-color: #5bc0de; } span.metric-info.highlighted { background-color: #5bc0de; } div.metric-warning, span.metric-warning { color: #f0ad4e; border-color: #f0ad4e; } span.metric-warning.highlighted { background-color: #f0ad4e; } div.metric-success, span.metric-success { color: #5cb85c; border-color: #5cb85c; } span.metric-success.highlighted { background-color: #5cb85c; } div.metric-danger, span.metric-danger { color: #d9534f; border-color: #d9534f; } span.metric-danger.highlighted { background-color: #d9534f; } span.metric-null, div.metric-null { display: none; } @media (min-width: 992px) { #stats { position: fixed; width: 220px } } @media (min-width: 1200px) { #stats { width: 262.5px; } } /* Recurring Jobs Page */ .cron-badge { display: inline-block; max-width: 125px; white-space: normal; word-break: break-word; } /* Full width styles */ @media (min-width: 768px) { .container { width: 100%; padding-left: 25px; padding-right: 25px; } } @media (min-width: 992px) { #wrap > .container > .row > .col-md-3 { width: 251px; } #wrap > .container > .row > .col-md-9 { width: calc(100% - 251px); } } @media (min-width: 1200px) { #wrap > .container > .row > .col-md-3 { width: 293px; } #wrap > .container > .row > .col-md-9 { width: calc(100% - 293px); } } ================================================ FILE: src/Hangfire.Core/Dashboard/Content/js/hangfire.js ================================================ (function (hangfire) { var changeDatasetColorScheme = function(newColorScheme) { this._chart.data.datasets[0].backgroundColor = COLORS[newColorScheme].failed.backgroundColor; this._chart.data.datasets[0].borderColor = COLORS[newColorScheme].failed.borderColor; this._chart.data.datasets[1].backgroundColor = COLORS[newColorScheme].deleted.backgroundColor; this._chart.data.datasets[1].borderColor = COLORS[newColorScheme].deleted.borderColor; this._chart.data.datasets[2].backgroundColor = COLORS[newColorScheme].succeeded.backgroundColor; this._chart.data.datasets[2].borderColor = COLORS[newColorScheme].succeeded.borderColor; this._chart.options.scales.xAxes[0].gridLines.color = COLORS[newColorScheme].cartesianColor; this._chart.options.scales.yAxes[0].gridLines.color = COLORS[newColorScheme].cartesianColor; this._chart.update(); } var COLORS = { light: { cartesianColor: '#e5e5e5', failed: { backgroundColor: '#D55251', borderColor: null, }, deleted: { backgroundColor: '#919191', borderColor: null, }, succeeded: { backgroundColor: '#6FCD6D', borderColor: '#62B35F', }, }, dark: { cartesianColor: '#5f5f5f', failed: { backgroundColor: 'rgba(215, 58, 74, 0.4)', }, deleted: { backgroundColor: 'rgba(204, 204, 204, 0.4)', }, succeeded: { backgroundColor: 'rgba(87, 171, 90, 0.4)', borderColor: 'rgba(87, 171, 90, 1)', }, }, }; hangfire.config = { pollInterval: $("#hangfireConfig").data("pollinterval"), pollUrl: $("#hangfireConfig").data("pollurl"), locale: document.documentElement.lang, darkMode: $("#hangfireConfig").data("darkmode") }; var colorScheme = "light"; if (hangfire.config.darkMode) { if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { colorScheme = "dark"; } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { colorScheme = e.matches ? "dark" : "light"; hangfire.page.realtimeGraph.changeDatasetColorScheme(colorScheme); hangfire.page.historyGraph.changeDatasetColorScheme(colorScheme); }); } hangfire.ErrorAlert = (function () { function ErrorAlert(title, message) { this._errorAlert = $('#errorAlert'); this._errorAlertTitle = $('#errorAlertTitle'); this._errorAlertMessage = $('#errorAlertMessage'); this._title = title; this._message = message; } ErrorAlert.prototype.show = function() { this._errorAlertTitle.html(this._title); this._errorAlertMessage.html(this._message); $('#errorAlert').show(); var alertHeight = $('#errorAlert').outerHeight(); $('#errorAlert').hide(); $('#errorAlert').slideDown("fast"); $('.js-page-container').animate({ 'padding-top': alertHeight + 'px' }, "fast"); }; return ErrorAlert; })(); hangfire.Metrics = (function() { function Metrics() { this._metrics = {}; } Metrics.prototype.addElement = function(name, element) { if (!(name in this._metrics)) { this._metrics[name] = []; } this._metrics[name].push(element); }; Metrics.prototype.getElements = function(name) { if (!(name in this._metrics)) { return []; } return this._metrics[name]; }; Metrics.prototype.getNames = function() { var result = []; var metrics = this._metrics; for (var name in metrics) { if (metrics.hasOwnProperty(name)) { result.push(name); } } return result; }; return Metrics; })(); hangfire.RealtimeGraph = (function() { function RealtimeGraph(element, succeeded, failed, deleted, succeededStr, failedStr, deletedStr, pollInterval) { this._succeeded = succeeded; this._failed = failed; this._deleted = deleted; this._last = Date.now(); this._pollInterval = pollInterval; this._chart = new Chart(element, { type: 'line', data: { datasets: [ { label: failedStr, borderColor: COLORS[colorScheme].failed.borderColor, backgroundColor: COLORS[colorScheme].failed.backgroundColor, borderWidth: 2 }, { label: deletedStr, borderColor: COLORS[colorScheme].deleted.borderColor, backgroundColor: COLORS[colorScheme].deleted.backgroundColor, borderWidth: 2 }, { label: succeededStr, borderColor: COLORS[colorScheme].succeeded.borderColor, backgroundColor: COLORS[colorScheme].succeeded.backgroundColor }, ] }, options: { scales: { xAxes: [{ gridLines: { color: COLORS[colorScheme].cartesianColor }, type: 'realtime', realtime: { duration: 60 * 1000, delay: pollInterval }, time: { unit: 'second', tooltipFormat: 'LL LTS', displayFormats: { second: 'LTS', minute: 'LTS' } }, ticks: { maxRotation: 0 } }], yAxes: [{ gridLines: { color: COLORS[colorScheme].cartesianColor }, ticks: { beginAtZero: true, precision: 0, min: 0, maxTicksLimit: 6, suggestedMax: 10 }, stacked: true }] }, elements: { line: { tension: 0 }, point: { radius: 0 } }, animation: { duration: 0 }, hover: { animationDuration: 0 }, responsiveAnimationDuration: 0, legend: { display: false }, tooltips: { mode: 'index', intersect: false } } }); } RealtimeGraph.prototype.appendHistory = function (statistics) { var newFailed = parseInt(statistics["failed:count"].intValue); var newDeleted = parseInt(statistics["deleted:count"].intValue); var newSucceeded = parseInt(statistics["succeeded:count"].intValue); var now = Date.now(); if (this._succeeded !== null && this._failed !== null && this._deleted !== null && (now - this._last < this._pollInterval * 2)) { var failed = Math.max(newFailed - this._failed, 0); var deleted = Math.max(newDeleted - this._deleted, 0); var succeeded = Math.max(newSucceeded - this._succeeded, 0); this._chart.data.datasets[0].data.push({ x: now, y: failed }); this._chart.data.datasets[1].data.push({ x: now, y: deleted }); this._chart.data.datasets[2].data.push({ x: now, y: succeeded }); this._chart.update(); } this._failed = newFailed; this._deleted = newDeleted; this._succeeded = newSucceeded; this._last = now; }; RealtimeGraph.prototype.changeDatasetColorScheme = changeDatasetColorScheme; return RealtimeGraph; })(); hangfire.HistoryGraph = (function() { function HistoryGraph(element, succeeded, failed, deleted, succeededStr, failedStr, deletedStr) { var timeOptions = $(element).data('period') === 'week' ? { unit: 'day', tooltipFormat: 'LL', displayFormats: { day: 'll' } } : { unit: 'hour', tooltipFormat: 'LLL', displayFormats: { hour: 'LT', day: 'll' } }; this._chart = new Chart(element, { type: 'line', data: { datasets: [ { label: failedStr, borderColor: COLORS[colorScheme].failed.borderColor, backgroundColor: COLORS[colorScheme].failed.backgroundColor, borderWidth: 2, data: failed, }, { label: deletedStr, borderColor: COLORS[colorScheme].deleted.borderColor, backgroundColor: COLORS[colorScheme].deleted.backgroundColor, borderWidth: 2, data: deleted, }, { label: succeededStr, borderColor: COLORS[colorScheme].succeeded.borderColor, backgroundColor: COLORS[colorScheme].succeeded.backgroundColor, data: succeeded, }, ] }, options: { scales: { xAxes: [{ gridLines: { color: COLORS[colorScheme].cartesianColor }, type: 'time', time: timeOptions, ticks: { maxRotation: 0 } }], yAxes: [{ gridLines: { color: COLORS[colorScheme].cartesianColor }, ticks: { beginAtZero: true, precision: 0, maxTicksLimit: 6 }, stacked: true }] }, elements: { line: { tension: 0 }, point: { radius: 0 } }, legend: { display: false }, tooltips: { mode: 'index', intersect: false }, plugins: { streaming: false }, } }); HistoryGraph.prototype.changeDatasetColorScheme = changeDatasetColorScheme; } return HistoryGraph; })(); hangfire.StatisticsPoller = (function() { function StatisticsPoller(metricsCallback, statisticsUrl, pollInterval) { this._metricsCallback = metricsCallback; this._listeners = []; this._statisticsUrl = statisticsUrl; this._pollInterval = pollInterval; this._timeoutId = null; } StatisticsPoller.prototype.start = function () { var self = this; var intervalFunc = function() { try { $.post(self._statisticsUrl, { metrics: self._metricsCallback() }) .done(function (data) { self._notifyListeners(data); if (self._timeoutId !== null) { self._timeoutId = setTimeout(intervalFunc, self._pollInterval); } }) .fail(function (xhr) { var errorAlert = new Hangfire.ErrorAlert( 'Unable to refresh the statistics:', 'the server responded with ' + xhr.status + ' (' + xhr.statusText + '). Try reloading the page manually, or wait for automatic reload that will happen in a minute.'); errorAlert.show(); self._timeoutId = null; setTimeout(function() { window.location.reload(); }, 60*1000); }); } catch (e) { console.log(e); } }; this._timeoutId = setTimeout(intervalFunc, this._pollInterval); }; StatisticsPoller.prototype.stop = function() { if (this._timeoutId !== null) { clearTimeout(this._timeoutId); this._timeoutId = null; } }; StatisticsPoller.prototype.addListener = function(listener) { this._listeners.push(listener); }; StatisticsPoller.prototype._notifyListeners = function(statistics) { var length = this._listeners.length; var i; for (i = 0; i < length; i++) { this._listeners[i](statistics); } }; return StatisticsPoller; })(); hangfire.Page = (function() { function Page(config) { this._metrics = new Hangfire.Metrics(); var self = this; this._poller = new Hangfire.StatisticsPoller( function () { return self._metrics.getNames(); }, config.pollUrl, config.pollInterval); this._initialize(config.locale); this.realtimeGraph = this._createRealtimeGraph('realtimeGraph', config.pollInterval); this.historyGraph = this._createHistoryGraph('historyGraph'); this._poller.start(); }; Page.prototype._createRealtimeGraph = function(elementId, pollInterval) { var realtimeElement = document.getElementById(elementId); if (realtimeElement) { var succeeded = parseInt($(realtimeElement).data('succeeded')); var failed = parseInt($(realtimeElement).data('failed')); var deleted = parseInt($(realtimeElement).data('deleted')); var succeededStr = $(realtimeElement).data('succeeded-string'); var failedStr = $(realtimeElement).data('failed-string'); var deletedStr = $(realtimeElement).data('deleted-string'); var realtimeGraph = new Hangfire.RealtimeGraph(realtimeElement, succeeded, failed, deleted, succeededStr, failedStr, deletedStr, pollInterval); this._poller.addListener(function (data) { realtimeGraph.appendHistory(data); }); return realtimeGraph; } return null; }; Page.prototype._createHistoryGraph = function(elementId) { var historyElement = document.getElementById(elementId); if (historyElement) { var createSeries = function (obj) { var series = []; for (var date in obj) { if (obj.hasOwnProperty(date)) { var value = obj[date]; var point = { x: Date.parse(date), y: value }; series.unshift(point); } } return series; }; var succeeded = createSeries($(historyElement).data("succeeded")); var failed = createSeries($(historyElement).data("failed")); var deleted = createSeries($(historyElement).data("deleted")); var succeededStr = $(historyElement).data('succeeded-string'); var failedStr = $(historyElement).data('failed-string'); var deletedStr = $(historyElement).data('deleted-string'); return new Hangfire.HistoryGraph(historyElement, succeeded, failed, deleted, succeededStr, failedStr, deletedStr); } return null; }; Page.prototype._initialize = function (locale) { moment.locale(locale); var updateRelativeDates = function () { $('*[data-moment]').each(function () { var $this = $(this); var timestamp = $this.data('moment'); if (timestamp) { var time = moment(timestamp, 'X'); $this.html(time.fromNow()) .attr('title', time.format('llll')) .attr('data-container', 'body'); } }); $('*[data-moment-title]').each(function () { var $this = $(this); var timestamp = $this.data('moment-title'); if (timestamp) { var time = moment(timestamp, 'X'); $this.prop('title', time.format('llll')) .attr('data-container', 'body'); } }); $('*[data-moment-local]').each(function () { var $this = $(this); var timestamp = $this.data('moment-local'); if (timestamp) { var time = moment(timestamp, 'X'); $this.html(time.format('l LTS')); } }); }; updateRelativeDates(); setInterval(updateRelativeDates, 30 * 1000); $('*[title]').tooltip(); var self = this; $('*[data-metric]').each(function () { var name = $(this).data('metric'); self._metrics.addElement(name, this); }); this._poller.addListener(function (metrics) { for (var name in metrics) { var elements = self._metrics.getElements(name); for (var i = 0; i < elements.length; i++) { var metric = metrics[name]; var metricClass = metric ? "metric-" + metric.style : "metric-null"; var highlighted = metric && metric.highlighted ? "highlighted" : null; var value = metric ? metric.value : null; $(elements[i]) .text(value) .closest('.metric') .removeClass() .addClass(["metric", metricClass, highlighted].join(' ')); } } }); var csrfHeader = $('meta[name="csrf-header"]').attr('content'); var csrfToken = $('meta[name="csrf-token"]').attr('content'); if (csrfToken && csrfHeader) { var headers = {}; headers[csrfHeader] = csrfToken; $.ajaxSetup({ headers: headers }); } $(document).on('click', '*[data-ajax]', function (e) { var $this = $(this); var confirmText = $this.data('confirm'); if (!confirmText || confirm(confirmText)) { $this.prop('disabled'); var loadingDelay = setTimeout(function() { $this.button('loading'); }, 100); $.post($this.data('ajax'), function() { clearTimeout(loadingDelay); window.location.reload(); }); } e.preventDefault(); }); $(document).on('click', '.expander', function (e) { var $expander = $(this), $expandable = $expander.closest('tr').next().find('.expandable'); if (!$expandable.is(':visible')) { $expander.text('Fewer details...'); } $expandable.slideToggle( 150, function() { if (!$expandable.is(':visible')) { $expander.text('More details...'); } }); e.preventDefault(); }); $('.js-jobs-list').each(function () { var container = this; var selectRow = function(row, isSelected) { var $checkbox = $('.js-jobs-list-checkbox', row); if ($checkbox.length > 0) { $checkbox.prop('checked', isSelected); $(row).toggleClass('highlight', isSelected); } }; var toggleRowSelection = function(row) { var $checkbox = $('.js-jobs-list-checkbox', row); if ($checkbox.length > 0) { var isSelected = $checkbox.is(':checked'); selectRow(row, !isSelected); } }; var setListState = function (state) { $('.js-jobs-list-select-all', container) .prop('checked', state === 'all-selected') .prop('indeterminate', state === 'some-selected'); $('.js-jobs-list-command', container) .prop('disabled', state === 'none-selected'); }; var updateListState = function() { var selectedRows = $('.js-jobs-list-checkbox', container).map(function() { return $(this).prop('checked'); }).get(); var state = 'none-selected'; if (selectedRows.length > 0) { state = 'some-selected'; if ($.inArray(false, selectedRows) === -1) { state = 'all-selected'; } else if ($.inArray(true, selectedRows) === -1) { state = 'none-selected'; } } setListState(state); }; $(this).on('click', '.js-jobs-list-checkbox', function(e) { selectRow( $(this).closest('.js-jobs-list-row').first(), $(this).is(':checked')); updateListState(); e.stopPropagation(); }); $(this).on('click', '.js-jobs-list-row', function (e) { if ($(e.target).is('a')) return; toggleRowSelection(this); updateListState(); }); $(this).on('click', '.js-jobs-list-select-all', function() { var selectRows = $(this).is(':checked'); $('.js-jobs-list-row', container).each(function() { selectRow(this, selectRows); }); updateListState(); }); $(this).on('click', '.js-jobs-list-command', function(e) { var $this = $(this); var confirmText = $this.data('confirm'); var jobs = $("input[name='jobs[]']:checked", container).map(function() { return $(this).val(); }).get(); if (!confirmText || confirm(confirmText)) { $this.prop('disabled'); var loadingDelay = setTimeout(function () { $this.button('loading'); }, 100); $.post($this.data('url'), { 'jobs[]': jobs }, function () { clearTimeout(loadingDelay); window.location.reload(); }); } e.preventDefault(); }); updateListState(); }); }; return Page; })(); })(window.Hangfire = window.Hangfire || {}); $(function () { Hangfire.page = new Hangfire.Page(Hangfire.config); }); ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Resources { using System; using System.Reflection; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Strings { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Strings() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Hangfire.Dashboard.Content.resx.Strings", typeof(Strings).GetTypeInfo().Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Looks up a localized string similar to Don't worry, continuations are working as expected. But your current job storage does not support some queries required to show this page. Please try to update your storage or wait until the full command set is implemented.. /// public static string AwaitingJobsPage_ContinuationsWarning_Text { get { return ResourceManager.GetString("AwaitingJobsPage_ContinuationsWarning_Text", resourceCulture); } } /// /// Looks up a localized string similar to Continuations are working, but this page can't be displayed. /// public static string AwaitingJobsPage_ContinuationsWarning_Title { get { return ResourceManager.GetString("AwaitingJobsPage_ContinuationsWarning_Title", resourceCulture); } } /// /// Looks up a localized string similar to No jobs found in awaiting state.. /// public static string AwaitingJobsPage_NoJobs { get { return ResourceManager.GetString("AwaitingJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Options. /// public static string AwaitingJobsPage_Table_Options { get { return ResourceManager.GetString("AwaitingJobsPage_Table_Options", resourceCulture); } } /// /// Looks up a localized string similar to Parent. /// public static string AwaitingJobsPage_Table_Parent { get { return ResourceManager.GetString("AwaitingJobsPage_Table_Parent", resourceCulture); } } /// /// Looks up a localized string similar to Since. /// public static string AwaitingJobsPage_Table_Since { get { return ResourceManager.GetString("AwaitingJobsPage_Table_Since", resourceCulture); } } /// /// Looks up a localized string similar to Awaiting Jobs. /// public static string AwaitingJobsPage_Title { get { return ResourceManager.GetString("AwaitingJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to Can not find the target method.. /// public static string Common_CannotFindTargetMethod { get { return ResourceManager.GetString("Common_CannotFindTargetMethod", resourceCulture); } } /// /// Looks up a localized string similar to Condition. /// public static string Common_Condition { get { return ResourceManager.GetString("Common_Condition", resourceCulture); } } /// /// Looks up a localized string similar to Continuations. /// public static string Common_Continuations { get { return ResourceManager.GetString("Common_Continuations", resourceCulture); } } /// /// Looks up a localized string similar to Created. /// public static string Common_Created { get { return ResourceManager.GetString("Common_Created", resourceCulture); } } /// /// Looks up a localized string similar to Delete. /// public static string Common_Delete { get { return ResourceManager.GetString("Common_Delete", resourceCulture); } } /// /// Looks up a localized string similar to Do you really want to DELETE ALL selected jobs?. /// public static string Common_DeleteConfirm { get { return ResourceManager.GetString("Common_DeleteConfirm", resourceCulture); } } /// /// Looks up a localized string similar to Delete selected. /// public static string Common_DeleteSelected { get { return ResourceManager.GetString("Common_DeleteSelected", resourceCulture); } } /// /// Looks up a localized string similar to Deleting.... /// public static string Common_Deleting { get { return ResourceManager.GetString("Common_Deleting", resourceCulture); } } /// /// Looks up a localized string similar to Disabled. /// public static string Common_Disabled { get { return ResourceManager.GetString("Common_Disabled", resourceCulture); } } /// /// Looks up a localized string similar to Enqueue jobs. /// public static string Common_EnqueueButton_Text { get { return ResourceManager.GetString("Common_EnqueueButton_Text", resourceCulture); } } /// /// Looks up a localized string similar to Enqueued. /// public static string Common_Enqueued { get { return ResourceManager.GetString("Common_Enqueued", resourceCulture); } } /// /// Looks up a localized string similar to Enqueueing.... /// public static string Common_Enqueueing { get { return ResourceManager.GetString("Common_Enqueueing", resourceCulture); } } /// /// Looks up a localized string similar to Error. /// public static string Common_Error { get { return ResourceManager.GetString("Common_Error", resourceCulture); } } /// /// Looks up a localized string similar to Fetched. /// public static string Common_Fetched { get { return ResourceManager.GetString("Common_Fetched", resourceCulture); } } /// /// Looks up a localized string similar to Id. /// public static string Common_Id { get { return ResourceManager.GetString("Common_Id", resourceCulture); } } /// /// Looks up a localized string similar to Job. /// public static string Common_Job { get { return ResourceManager.GetString("Common_Job", resourceCulture); } } /// /// Looks up a localized string similar to Job expired.. /// public static string Common_JobExpired { get { return ResourceManager.GetString("Common_JobExpired", resourceCulture); } } /// /// Looks up a localized string similar to Job's state has been changed while fetching data.. /// public static string Common_JobStateChanged_Text { get { return ResourceManager.GetString("Common_JobStateChanged_Text", resourceCulture); } } /// /// Looks up a localized string similar to Fewer details.... /// public static string Common_LessDetails { get { return ResourceManager.GetString("Common_LessDetails", resourceCulture); } } /// /// Looks up a localized string similar to More details.... /// public static string Common_MoreDetails { get { return ResourceManager.GetString("Common_MoreDetails", resourceCulture); } } /// /// Looks up a localized string similar to No state. /// public static string Common_NoState { get { return ResourceManager.GetString("Common_NoState", resourceCulture); } } /// /// Looks up a localized string similar to N/A. /// public static string Common_NotAvailable { get { return ResourceManager.GetString("Common_NotAvailable", resourceCulture); } } /// /// Looks up a localized string similar to Day. /// public static string Common_PeriodDay { get { return ResourceManager.GetString("Common_PeriodDay", resourceCulture); } } /// /// Looks up a localized string similar to Week. /// public static string Common_PeriodWeek { get { return ResourceManager.GetString("Common_PeriodWeek", resourceCulture); } } /// /// Looks up a localized string similar to Reason. /// public static string Common_Reason { get { return ResourceManager.GetString("Common_Reason", resourceCulture); } } /// /// Looks up a localized string similar to Requeue jobs. /// public static string Common_RequeueJobs { get { return ResourceManager.GetString("Common_RequeueJobs", resourceCulture); } } /// /// Looks up a localized string similar to Retry. /// public static string Common_Retry { get { return ResourceManager.GetString("Common_Retry", resourceCulture); } } /// /// Looks up a localized string similar to Server. /// public static string Common_Server { get { return ResourceManager.GetString("Common_Server", resourceCulture); } } /// /// Looks up a localized string similar to State. /// public static string Common_State { get { return ResourceManager.GetString("Common_State", resourceCulture); } } /// /// Looks up a localized string similar to Unknown. /// public static string Common_Unknown { get { return ResourceManager.GetString("Common_Unknown", resourceCulture); } } /// /// Looks up a localized string similar to No deleted jobs found.. /// public static string DeletedJobsPage_NoJobs { get { return ResourceManager.GetString("DeletedJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Deleted. /// public static string DeletedJobsPage_Table_Deleted { get { return ResourceManager.GetString("DeletedJobsPage_Table_Deleted", resourceCulture); } } /// /// Looks up a localized string similar to Exception. /// public static string DeletedJobsPage_Table_Exception { get { return ResourceManager.GetString("DeletedJobsPage_Table_Exception", resourceCulture); } } /// /// Looks up a localized string similar to Deleted Jobs. /// public static string DeletedJobsPage_Title { get { return ResourceManager.GetString("DeletedJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to The queue is empty.. /// public static string EnqueuedJobsPage_NoJobs { get { return ResourceManager.GetString("EnqueuedJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Enqueued Jobs. /// public static string EnqueuedJobsPage_Title { get { return ResourceManager.GetString("EnqueuedJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to <strong>Failed jobs do not become expired</strong> to allow you to re-queue them without any /// time pressure. You should re-queue or delete them manually, or apply <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> /// attribute to delete them automatically.. /// public static string FailedJobsPage_FailedJobsNotExpire_Warning_Html { get { return ResourceManager.GetString("FailedJobsPage_FailedJobsNotExpire_Warning_Html", resourceCulture); } } /// /// Looks up a localized string similar to You have no failed jobs at the moment.. /// public static string FailedJobsPage_NoJobs { get { return ResourceManager.GetString("FailedJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Failed. /// public static string FailedJobsPage_Table_Failed { get { return ResourceManager.GetString("FailedJobsPage_Table_Failed", resourceCulture); } } /// /// Looks up a localized string similar to Failed Jobs. /// public static string FailedJobsPage_Title { get { return ResourceManager.GetString("FailedJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to The queue is empty.. /// public static string FetchedJobsPage_NoJobs { get { return ResourceManager.GetString("FetchedJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Fetched Jobs. /// public static string FetchedJobsPage_Title { get { return ResourceManager.GetString("FetchedJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to Failed. /// public static string HomePage_GraphHover_Failed { get { return ResourceManager.GetString("HomePage_GraphHover_Failed", resourceCulture); } } /// /// Looks up a localized string similar to Succeeded. /// public static string HomePage_GraphHover_Succeeded { get { return ResourceManager.GetString("HomePage_GraphHover_Succeeded", resourceCulture); } } /// /// Looks up a localized string similar to History Graph. /// public static string HomePage_HistoryGraph { get { return ResourceManager.GetString("HomePage_HistoryGraph", resourceCulture); } } /// /// Looks up a localized string similar to Realtime Graph. /// public static string HomePage_RealtimeGraph { get { return ResourceManager.GetString("HomePage_RealtimeGraph", resourceCulture); } } /// /// Looks up a localized string similar to Overview. /// public static string HomePage_Title { get { return ResourceManager.GetString("HomePage_Title", resourceCulture); } } /// /// Looks up a localized string similar to Created. /// public static string JobDetailsPage_Created { get { return ResourceManager.GetString("JobDetailsPage_Created", resourceCulture); } } /// /// Looks up a localized string similar to Do you really want to delete this job?. /// public static string JobDetailsPage_DeleteConfirm { get { return ResourceManager.GetString("JobDetailsPage_DeleteConfirm", resourceCulture); } } /// /// Looks up a localized string similar to <strong>The job was aborted</strong> – it is processed by server /// <code>{0}</code> which is not in the /// <a href="{1}">active servers</a> list for now. /// It will be retried automatically after invisibility timeout, but you can /// also re-queue or delete it manually.. /// public static string JobDetailsPage_JobAbortedNotActive_Warning_Html { get { return ResourceManager.GetString("JobDetailsPage_JobAbortedNotActive_Warning_Html", resourceCulture); } } /// /// Looks up a localized string similar to <strong>Looks like the job was aborted</strong> – it is processed by server /// <code>{0}</code>, which reported its heartbeat more than 1 minute ago. /// It will be retried automatically after invisibility timeout, but you can /// also re-queue or delete it manually.. /// public static string JobDetailsPage_JobAbortedWithHeartbeat_Warning_Html { get { return ResourceManager.GetString("JobDetailsPage_JobAbortedWithHeartbeat_Warning_Html", resourceCulture); } } /// /// Looks up a localized string similar to Background job '{0}' has expired or could not be found on the server.. /// public static string JobDetailsPage_JobExpired { get { return ResourceManager.GetString("JobDetailsPage_JobExpired", resourceCulture); } } /// /// Looks up a localized string similar to <strong>The job is finished</strong>. /// It will be removed automatically <em><abbr data-moment="{0}">{1}</abbr></em>.. /// public static string JobDetailsPage_JobFinished_Warning_Html { get { return ResourceManager.GetString("JobDetailsPage_JobFinished_Warning_Html", resourceCulture); } } /// /// Looks up a localized string similar to Id. /// public static string JobDetailsPage_JobId { get { return ResourceManager.GetString("JobDetailsPage_JobId", resourceCulture); } } /// /// Looks up a localized string similar to Parameters. /// public static string JobDetailsPage_Parameters { get { return ResourceManager.GetString("JobDetailsPage_Parameters", resourceCulture); } } /// /// Looks up a localized string similar to Requeue. /// public static string JobDetailsPage_Requeue { get { return ResourceManager.GetString("JobDetailsPage_Requeue", resourceCulture); } } /// /// Looks up a localized string similar to State. /// public static string JobDetailsPage_State { get { return ResourceManager.GetString("JobDetailsPage_State", resourceCulture); } } /// /// Looks up a localized string similar to Awaiting. /// public static string JobsSidebarMenu_Awaiting { get { return ResourceManager.GetString("JobsSidebarMenu_Awaiting", resourceCulture); } } /// /// Looks up a localized string similar to Deleted. /// public static string JobsSidebarMenu_Deleted { get { return ResourceManager.GetString("JobsSidebarMenu_Deleted", resourceCulture); } } /// /// Looks up a localized string similar to Enqueued. /// public static string JobsSidebarMenu_Enqueued { get { return ResourceManager.GetString("JobsSidebarMenu_Enqueued", resourceCulture); } } /// /// Looks up a localized string similar to Failed. /// public static string JobsSidebarMenu_Failed { get { return ResourceManager.GetString("JobsSidebarMenu_Failed", resourceCulture); } } /// /// Looks up a localized string similar to Processing. /// public static string JobsSidebarMenu_Processing { get { return ResourceManager.GetString("JobsSidebarMenu_Processing", resourceCulture); } } /// /// Looks up a localized string similar to Scheduled. /// public static string JobsSidebarMenu_Scheduled { get { return ResourceManager.GetString("JobsSidebarMenu_Scheduled", resourceCulture); } } /// /// Looks up a localized string similar to Succeeded. /// public static string JobsSidebarMenu_Succeeded { get { return ResourceManager.GetString("JobsSidebarMenu_Succeeded", resourceCulture); } } /// /// Looks up a localized string similar to Back to site. /// public static string LayoutPage_Back { get { return ResourceManager.GetString("LayoutPage_Back", resourceCulture); } } /// /// Looks up a localized string similar to Generated: {0}ms. /// public static string LayoutPage_Footer_Generatedms { get { return ResourceManager.GetString("LayoutPage_Footer_Generatedms", resourceCulture); } } /// /// Looks up a localized string similar to Storage Time:. /// public static string LayoutPage_Footer_StorageTime { get { return ResourceManager.GetString("LayoutPage_Footer_StorageTime", resourceCulture); } } /// /// Looks up a localized string similar to Application Time:. /// public static string LayoutPage_Footer_Time { get { return ResourceManager.GetString("LayoutPage_Footer_Time", resourceCulture); } } /// /// Looks up a localized string similar to Application time is out of sync with storage time. /// public static string LayoutPage_Footer_TimeIsOutOfSync { get { return ResourceManager.GetString("LayoutPage_Footer_TimeIsOutOfSync", resourceCulture); } } /// /// Looks up a localized string similar to Active Connections. /// public static string Metrics_ActiveConnections { get { return ResourceManager.GetString("Metrics_ActiveConnections", resourceCulture); } } /// /// Looks up a localized string similar to Awaiting. /// public static string Metrics_AwaitingCount { get { return ResourceManager.GetString("Metrics_AwaitingCount", resourceCulture); } } /// /// Looks up a localized string similar to Deleted Jobs. /// public static string Metrics_DeletedJobs { get { return ResourceManager.GetString("Metrics_DeletedJobs", resourceCulture); } } /// /// Looks up a localized string similar to Enqueued. /// public static string Metrics_EnqueuedCountOrNull { get { return ResourceManager.GetString("Metrics_EnqueuedCountOrNull", resourceCulture); } } /// /// Looks up a localized string similar to Enqueued / Queues. /// public static string Metrics_EnqueuedQueuesCount { get { return ResourceManager.GetString("Metrics_EnqueuedQueuesCount", resourceCulture); } } /// /// Looks up a localized string similar to {0} failed job(s) found. Retry or delete them manually.. /// public static string Metrics_FailedCountOrNull { get { return ResourceManager.GetString("Metrics_FailedCountOrNull", resourceCulture); } } /// /// Looks up a localized string similar to Failed Jobs. /// public static string Metrics_FailedJobs { get { return ResourceManager.GetString("Metrics_FailedJobs", resourceCulture); } } /// /// Looks up a localized string similar to Processing Jobs. /// public static string Metrics_ProcessingJobs { get { return ResourceManager.GetString("Metrics_ProcessingJobs", resourceCulture); } } /// /// Looks up a localized string similar to Recurring Jobs. /// public static string Metrics_RecurringJobs { get { return ResourceManager.GetString("Metrics_RecurringJobs", resourceCulture); } } /// /// Looks up a localized string similar to Retries. /// public static string Metrics_Retries { get { return ResourceManager.GetString("Metrics_Retries", resourceCulture); } } /// /// Looks up a localized string similar to Scheduled Jobs. /// public static string Metrics_ScheduledJobs { get { return ResourceManager.GetString("Metrics_ScheduledJobs", resourceCulture); } } /// /// Looks up a localized string similar to Servers. /// public static string Metrics_Servers { get { return ResourceManager.GetString("Metrics_Servers", resourceCulture); } } /// /// Looks up a localized string similar to Active Transactions. /// public static string Metrics_SQLServer_ActiveTransactions { get { return ResourceManager.GetString("Metrics_SQLServer_ActiveTransactions", resourceCulture); } } /// /// Looks up a localized string similar to Data File(s) Used (MB). /// public static string Metrics_SQLServer_DataFilesSize { get { return ResourceManager.GetString("Metrics_SQLServer_DataFilesSize", resourceCulture); } } /// /// Looks up a localized string similar to Log File(s) Used (MB). /// public static string Metrics_SQLServer_LogFilesSize { get { return ResourceManager.GetString("Metrics_SQLServer_LogFilesSize", resourceCulture); } } /// /// Looks up a localized string similar to Schema Version. /// public static string Metrics_SQLServer_SchemaVersion { get { return ResourceManager.GetString("Metrics_SQLServer_SchemaVersion", resourceCulture); } } /// /// Looks up a localized string similar to Succeeded Jobs. /// public static string Metrics_SucceededJobs { get { return ResourceManager.GetString("Metrics_SucceededJobs", resourceCulture); } } /// /// Looks up a localized string similar to Total Connections. /// public static string Metrics_TotalConnections { get { return ResourceManager.GetString("Metrics_TotalConnections", resourceCulture); } } /// /// Looks up a localized string similar to Jobs. /// public static string NavigationMenu_Jobs { get { return ResourceManager.GetString("NavigationMenu_Jobs", resourceCulture); } } /// /// Looks up a localized string similar to Recurring Jobs. /// public static string NavigationMenu_RecurringJobs { get { return ResourceManager.GetString("NavigationMenu_RecurringJobs", resourceCulture); } } /// /// Looks up a localized string similar to Retries. /// public static string NavigationMenu_Retries { get { return ResourceManager.GetString("NavigationMenu_Retries", resourceCulture); } } /// /// Looks up a localized string similar to Servers. /// public static string NavigationMenu_Servers { get { return ResourceManager.GetString("NavigationMenu_Servers", resourceCulture); } } /// /// Looks up a localized string similar to Next. /// public static string Paginator_Next { get { return ResourceManager.GetString("Paginator_Next", resourceCulture); } } /// /// Looks up a localized string similar to Prev. /// public static string Paginator_Prev { get { return ResourceManager.GetString("Paginator_Prev", resourceCulture); } } /// /// Looks up a localized string similar to Total items. /// public static string Paginator_TotalItems { get { return ResourceManager.GetString("Paginator_TotalItems", resourceCulture); } } /// /// Looks up a localized string similar to Items per page. /// public static string PerPageSelector_ItemsPerPage { get { return ResourceManager.GetString("PerPageSelector_ItemsPerPage", resourceCulture); } } /// /// Looks up a localized string similar to Looks like the job was aborted. /// public static string ProcessingJobsPage_Aborted { get { return ResourceManager.GetString("ProcessingJobsPage_Aborted", resourceCulture); } } /// /// Looks up a localized string similar to No jobs are being processed right now.. /// public static string ProcessingJobsPage_NoJobs { get { return ResourceManager.GetString("ProcessingJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Started. /// public static string ProcessingJobsPage_Table_Started { get { return ResourceManager.GetString("ProcessingJobsPage_Table_Started", resourceCulture); } } /// /// Looks up a localized string similar to Processing Jobs. /// public static string ProcessingJobsPage_Title { get { return ResourceManager.GetString("ProcessingJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to No jobs queued.. /// public static string QueuesPage_NoJobs { get { return ResourceManager.GetString("QueuesPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to No queued jobs found. Try to enqueue a job.. /// public static string QueuesPage_NoQueues { get { return ResourceManager.GetString("QueuesPage_NoQueues", resourceCulture); } } /// /// Looks up a localized string similar to Length. /// public static string QueuesPage_Table_Length { get { return ResourceManager.GetString("QueuesPage_Table_Length", resourceCulture); } } /// /// Looks up a localized string similar to Next jobs. /// public static string QueuesPage_Table_NextsJobs { get { return ResourceManager.GetString("QueuesPage_Table_NextsJobs", resourceCulture); } } /// /// Looks up a localized string similar to Queue. /// public static string QueuesPage_Table_Queue { get { return ResourceManager.GetString("QueuesPage_Table_Queue", resourceCulture); } } /// /// Looks up a localized string similar to Queues. /// public static string QueuesPage_Title { get { return ResourceManager.GetString("QueuesPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to Canceled. /// public static string RecurringJobsPage_Canceled { get { return ResourceManager.GetString("RecurringJobsPage_Canceled", resourceCulture); } } /// /// Looks up a localized string similar to No recurring jobs found.. /// public static string RecurringJobsPage_NoJobs { get { return ResourceManager.GetString("RecurringJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Cron expression is invalid or don't have any occurrences over the next 100 years. /// public static string RecurringJobsPage_RecurringJobDisabled_Tooltip { get { return ResourceManager.GetString("RecurringJobsPage_RecurringJobDisabled_Tooltip", resourceCulture); } } /// /// Looks up a localized string similar to Cron. /// public static string RecurringJobsPage_Table_Cron { get { return ResourceManager.GetString("RecurringJobsPage_Table_Cron", resourceCulture); } } /// /// Looks up a localized string similar to Last execution. /// public static string RecurringJobsPage_Table_LastExecution { get { return ResourceManager.GetString("RecurringJobsPage_Table_LastExecution", resourceCulture); } } /// /// Looks up a localized string similar to Next execution. /// public static string RecurringJobsPage_Table_NextExecution { get { return ResourceManager.GetString("RecurringJobsPage_Table_NextExecution", resourceCulture); } } /// /// Looks up a localized string similar to Time zone. /// public static string RecurringJobsPage_Table_TimeZone { get { return ResourceManager.GetString("RecurringJobsPage_Table_TimeZone", resourceCulture); } } /// /// Looks up a localized string similar to Recurring Jobs. /// public static string RecurringJobsPage_Title { get { return ResourceManager.GetString("RecurringJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to Triggering.... /// public static string RecurringJobsPage_Triggering { get { return ResourceManager.GetString("RecurringJobsPage_Triggering", resourceCulture); } } /// /// Looks up a localized string similar to Trigger now. /// public static string RecurringJobsPage_TriggerNow { get { return ResourceManager.GetString("RecurringJobsPage_TriggerNow", resourceCulture); } } /// /// Looks up a localized string similar to All is OK – you have no retries.. /// public static string RetriesPage_NoJobs { get { return ResourceManager.GetString("RetriesPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Retries. /// public static string RetriesPage_Title { get { return ResourceManager.GetString("RetriesPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to <h4>Retries are working, but this page can't be displayed</h4> /// <p> /// Don't worry, retries are working as expected. Your current job storage does not support /// some queries required to show this page. Please try to update your storage or wait until /// the full command set is implemented. /// </p> /// <p> /// Please go to the <a href="{0}">Scheduled jobs</a> page to see all the /// scheduled jobs including retries. /// </p>. /// public static string RetriesPage_Warning_Html { get { return ResourceManager.GetString("RetriesPage_Warning_Html", resourceCulture); } } /// /// Looks up a localized string similar to Enqueue now. /// public static string ScheduledJobsPage_EnqueueNow { get { return ResourceManager.GetString("ScheduledJobsPage_EnqueueNow", resourceCulture); } } /// /// Looks up a localized string similar to There are no scheduled jobs.. /// public static string ScheduledJobsPage_NoJobs { get { return ResourceManager.GetString("ScheduledJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Enqueue. /// public static string ScheduledJobsPage_Table_Enqueue { get { return ResourceManager.GetString("ScheduledJobsPage_Table_Enqueue", resourceCulture); } } /// /// Looks up a localized string similar to Scheduled. /// public static string ScheduledJobsPage_Table_Scheduled { get { return ResourceManager.GetString("ScheduledJobsPage_Table_Scheduled", resourceCulture); } } /// /// Looks up a localized string similar to Scheduled Jobs. /// public static string ScheduledJobsPage_Title { get { return ResourceManager.GetString("ScheduledJobsPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to Active. /// public static string ServersPage_Active { get { return ResourceManager.GetString("ServersPage_Active", resourceCulture); } } /// /// Looks up a localized string similar to There are no active servers. Background tasks will not be processed.. /// public static string ServersPage_NoServers { get { return ResourceManager.GetString("ServersPage_NoServers", resourceCulture); } } /// /// Looks up a localized string similar to Some of the servers don't have heartbeat reported within the last minute and may be aborted. If they don't report heartbeat in the near future, they will be removed automatically after timeout is exceeded, no manual action is required. /// Incomplete background jobs running on those servers will be re-queued automatically, but you can speed up the process by checking the <a href="{0}">Processing Jobs</a> page.. /// public static string ServersPage_Note_Text { get { return ResourceManager.GetString("ServersPage_Note_Text", resourceCulture); } } /// /// Looks up a localized string similar to Aborted servers will be removed automatically. /// public static string ServersPage_Note_Title { get { return ResourceManager.GetString("ServersPage_Note_Title", resourceCulture); } } /// /// Looks up a localized string similar to Possibly aborted. /// public static string ServersPage_Possibly_Aborted { get { return ResourceManager.GetString("ServersPage_Possibly_Aborted", resourceCulture); } } /// /// Looks up a localized string similar to Heartbeat. /// public static string ServersPage_Table_Heartbeat { get { return ResourceManager.GetString("ServersPage_Table_Heartbeat", resourceCulture); } } /// /// Looks up a localized string similar to Name. /// public static string ServersPage_Table_Name { get { return ResourceManager.GetString("ServersPage_Table_Name", resourceCulture); } } /// /// Looks up a localized string similar to Queues. /// public static string ServersPage_Table_Queues { get { return ResourceManager.GetString("ServersPage_Table_Queues", resourceCulture); } } /// /// Looks up a localized string similar to Started. /// public static string ServersPage_Table_Started { get { return ResourceManager.GetString("ServersPage_Table_Started", resourceCulture); } } /// /// Looks up a localized string similar to Workers. /// public static string ServersPage_Table_Workers { get { return ResourceManager.GetString("ServersPage_Table_Workers", resourceCulture); } } /// /// Looks up a localized string similar to Servers. /// public static string ServersPage_Title { get { return ResourceManager.GetString("ServersPage_Title", resourceCulture); } } /// /// Looks up a localized string similar to No succeeded jobs found.. /// public static string SucceededJobsPage_NoJobs { get { return ResourceManager.GetString("SucceededJobsPage_NoJobs", resourceCulture); } } /// /// Looks up a localized string similar to Duration. /// public static string SucceededJobsPage_Table_Duration { get { return ResourceManager.GetString("SucceededJobsPage_Table_Duration", resourceCulture); } } /// /// Looks up a localized string similar to Latency. /// public static string SucceededJobsPage_Table_Latency { get { return ResourceManager.GetString("SucceededJobsPage_Table_Latency", resourceCulture); } } /// /// Looks up a localized string similar to Succeeded. /// public static string SucceededJobsPage_Table_Succeeded { get { return ResourceManager.GetString("SucceededJobsPage_Table_Succeeded", resourceCulture); } } /// /// Looks up a localized string similar to Total Duration. /// public static string SucceededJobsPage_Table_TotalDuration { get { return ResourceManager.GetString("SucceededJobsPage_Table_TotalDuration", resourceCulture); } } /// /// Looks up a localized string similar to Succeeded Jobs. /// public static string SucceededJobsPage_Title { get { return ResourceManager.GetString("SucceededJobsPage_Title", resourceCulture); } } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.ca.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Les continuacions funcionen correctament, però el teu sistema d'emmagatzematge de tasques actual no suporta algunes consultes necessàries per a mostrar aquesta pàgina. Actualitzeu el vostre sistema d'emmagatzematge de tasques o espereu que aquest suporti aquesta funcionalitat Les continuacions funcionen, però aquesta pàgina no es pot mostrar. No s'han trobat tasques en espera Opcions Pare Fuzzy Tasques en espera Creat Eliminar ¿Esteu segur que voleu ELIMINAR TOTES les tasques seleccionades? Eliminant... Eliminar seleccionats Posar tasques a la cua Posant a la cua... Obtingut Fuzzy Id Tasca Tasca expirada L'estat de la tasca ha canviat mentre s'obtenia informació. Fuzzy Menys detalls Més detalls N/D Dia Setmana Raó Tornar a posar les tasques a la cua Fuzzy Tornar a intentar Servidor Estat Desconegut No s'han trobat tasques eliminades. Eliminat Tasques eliminades La cua esta buida. Tasques a la cua <strong>Les tasques fallides no expiren</strong> per a permetre-us tornar a afegir-les a la cua sense cap pressió. Podeu afegir-les de nou a la cua, eliminar-les manualment o aplicar l'atribut <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> per a eliminar-les automàticament. No hi han tasques amb errors. Amb errors Tasques amb errors La cua és buida Tasques obtingudes Fuzzy Gràfic històric Gràfic en temps real Panell Fuzzy Creat ¿Esteu segur que voleu eliminar aquesta tasca? Estat <strong>La tasca ha estat avortada</strong> - ha estat processada pel servidor <code>{0}</code> que no és a la llista de <a href="{1}">servidors actius</a> per ara. Serà recuperat automàticament després del període d'invisibilitat, però podeu tornar-la a posar en la cua o eliminar-la manualment. <strong>La tasca ha estat avortada</strong> - ha estat processada pel servido <code>{0}</code> que ha reportat un batec fa més d'un minut. Serà recuperat automàticament després del període d'invisibilitat, però podeu tornar-la a posar en la cua o eliminar-la manualment. La tasca amb id '{0}' ha expirat o no s'ha trobat al servidor <strong>La tasca ha finalitzat</strong>. S'eliminarà automàticament en <em><abbr data-moment="{0}">{1}</abbr></em> ID Tasca Tornar a posar a la cua Fuzzy Tornar Generat: {0}ms Hora: Següent Anterior Total elements Elements per pàgina Sembla que la tasca ha estat cancel·lada No hi han tasques en procés. Iniciat Tasques en procés No hi han tasques a la cua. No s'han trobat tasques a la cua. Proveu a posar alguna. Longitud Properes tasques Cua Cues Cancel·lat No s'han trobat tasques recurrents Cron Darrera execució Propera execució Zona horària Tasques recurrents Activant... Activar ara Tot va bé - no hi ha cap reintent. Reintents <h4>Els reintents estan funcionant, però aquesta pàgina no es pot mostrar</h4> <p> No us preocupeu, els reintents funcionen como haurien, però el teu sistema d'emmagatzematge de tasques actual no suporta algunes consultes necessàries per a mostrar aquesta pàgina. Actualitzeu el vostre sistema d'emmagatzematge o espereu fins que aquest suporti aquesta funcionalitat </p> <p> Visiteu la pàgina <a href="{0}">Tasques programades</a> per a veure totes les tasques programades, inclosos els reintents. </p> Posar a la cua ara No hi han tasques programades. Posar a la cua Programades Tasques programades No hi ha cap servidor actiu. Les tasques en segon pla no seran processades. Batec Fuzzy Nom Cues Iniciada Treballadors Fuzzy Servidors No s'han trobat tasques completades Completades Durada total Tasques completades A l'espera Eliminat Amb errors Processant Programades Completades Tasques Tasques recurrents Reintents Servidors No es pot trobar el mètode destí Fuzzy A la cua Sense estat A la cua Connexions actives Tasques eliminades Tasques fallides Tasques en procés Tasques recurrents Reintents Tasques programades Servidors Tasques completades Connexions totals Condició Continuacions A l'espera A la cua A la cua / Cues Amb errors Completats ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.de.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Erstellt Keine Sorge, die Ausführungen funktionieren wie erwartet. Die aktuelle Job Speicherung unterstützt jedoch einige Abfragen welche zum Anzeigen dieser Seite erforderlich sind NICHT. Bitte versuchen den Speicher zu aktualisieren oder warten bis der vollständige Ablauf implementiert ist. Weitere Ausführungen funktionieren. Nur diese Seite kann nicht angezeigt werden Keine Jobs im wartenden Zustand gefunden. Optionen Eltern Warten auf Jobs Löschen Wirklich ALLE ausgewählten Jobs LÖSCHEN? Löschen... Ausgewählte löschen In die Warteschlange Warteschlange abarbeiten... Abgerufen ID Job Job abgelaufen. Der Jobstatus wurde beim Abrufen von Daten geändert. Weniger Details... Weitere Details... N/A Tag Woche Grund Wieder in die Warteschlange Wiederholen Server Status Unbekannt Keine gelöschten Jobs gefunden. Gelöscht Gelöschte Jobs Die Warteschlange ist leer. Jobs in Warteschlange Zur Zeit gibt es keine fehlgeschlagenen Jobs. Fehler Fehlerhafte Jobs Die Warteschlange ist leer. Jobs abgerufen Zeigt für jede CPU-Auslastung ein Verlaufsdiagramm an. Echtzeitdiagramm Übersicht Erstellt Möchten Sie diesen Job wirklich löschen? Status <strong>Der Job wurde anscheinend abgebrochen</strong> - er wird vom Server verarbeitet <code>{0}</code>, welcher sich vor mehr als 1 Minute gemeldet hat. Es wird nach dem Timeout automatisch wiederholt. Alternativ kann der Job kann auch manuell in die Warteschlange gestellt oder löscht werden. Der Warteschlangen Job '{0}' ist abgelaufen oder wurde auf dem Server nicht gefunden. <strong>Der Job ist beendet</strong>. Er wird automatisch entfernt <em><abbr data-moment="{0}">{1}</abbr></em>. Job ID Erneut in Warteschlange einreihen Zurück zur Website Generiert: {0} ms Zeit: Weiter Zurück Gesamtzahl der Elemente Elemente pro Seite Der Job wurde anscheinend abgebrochen Derzeit werden keine Jobs verarbeitet. Gestartet Jobs bearbeiten Keine Jobs in der Warteschlange. Es wurden keine Jobs in der Warteschlange gefunden. Versuche einen Job in die Warteschlange zu stellen. Länge Nächster Job Warteschlange Warteschlangen Abgebrochen Keine wiederkehrenden Jobs gefunden. CRON-Ausdruck Zeitpunkt der letzten Ausführung Nächste Ausführung Zeitzone Wiederholte Jobs Auslöser Jetzt auslösen Ausführung abgeschlossen - Wiederholungsversuche notwendig. Erneute Versuche Jetzt in Warteschlange einfügen Es sind keine Jobs geplant. In der Warteschlange Geplant Geplante Jobs Es sind keine Server aktiv. Warteschlange kann nicht nicht verarbeitet werden. Rückmeldung Name Warteschlangen Gestartet Worker Server Es wurden keine erfolgreichen Jobs gefunden. Erfolgreich Gesamtdauer Erfolgreiche Jobs Warten Gelöscht Fehler Verarbeiten... Geplant Erfolgreich Aufträge Wiederholte Aufträge Erneute Versuche Server Die Zielmethode konnte nicht gefunden wurde. Zeit in Warteschlange Kein Zustand Zeit in Warteschlange Aktive Verbindungen Gelöschte Jobs Fehlerhafte Aufträge Warehouseverarbeitungsaufträge werden aktualisiert. Wiederholte Aufträge Erneute Versuche Geplante Aufträge Server Erfolgreiche Jobs Verbindungen gesamt Bedingung Ausführungen Warten in Warteschlange Warteschlangen {0} fehlgeschlagene Jobs gefunden. Manuell wiederholen oder löschen erforderlich. Fehler Erfolgreich Deaktiviert Cron Ausdruck ist ungültig Einige der Server haben innerhalb der letzten Minute keine Rückmeldung zurück gemeldet und werden möglicherweise abgebrochen. Wenn die Server in naher Zukunft keine Rückmeldung geben, werden sie automatisch entfernt, nachdem das Zeitlimit überschritten wurde. Es sind keine manuellen Maßnahmen erforderlich. Nicht aktive Server werden automatisch entfernt Aktiv Möglicherweise abgebrochen Fehler Parameter ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.es.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Las continuaciones funcionan correctamente, pero tu sistema de almacenamiento de tareas actual no soporta algunas consultas necesarias para mostrar esta página. Actualiza tu sistema de almacenamiento o espera hasta que éste soporte esta funcionalidad Las continuaciones funcionan, pero esta página no se puede mostrar. No se han encontrado tareas en espera Opciones Padre Fuzzy Tareas en espera Creado Eliminar ¿Estás seguro que quieres ELIMINAR TODAS las tareas seleccionadas? Eliminando... Eliminar seleccionados Poner tareas en la cola Poniendo en la cola... Obtenido Fuzzy Id Tarea Tarea expirada El estado de la tarea ha cambiado misentras se obtenia información. Fuzzy Menos detalles Más detalles N/D Día Semana Razón Volver a poner a la cola las tareas Fuzzy Reintentar Servidor Estado Desconocido No se han encontrado tareas eliminadas. Eliminado Tareas eliminadas La cola esta vacía. Tareas en la cola <strong>Las tareas fallidas no expiran</strong> para permititrte volver a añadirlas a la cola sin ninguna presión. Puedes añadirlas de nuevo a la cola, eliminarlas manualmente o aplicar el atributo <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> para eliminarlas automáticamente. No hay tareas con errores. Con errores Tareas con errores La cola está vacía Tareas obtenidas Fuzzy Gráfico histórico Gráfico en tiempo real Panel Fuzzy Creado ¿Estás seguro que quieres eliminar esta tarea? Estado <strong>La tarea ha sido abortada</strong> - ha sido procesada por el servidor <code>{0}</code> que no está en la lista de <a href="{1}">servidores activos</a> por ahora. Se recuperará automáticamente despues del periodo de invisibilidad, pero puedes volverla a poner en la cola o eliminarla manualmente. <strong>La tarea ha sido abortada</strong> - ha sido procesada por el servidor <code>{0}</code> que ha reportado un latido hace más de un minuto. Se recuperará automáticamente despues del periodo de invisibilidad, pero puedes volverla a poner en la cola o eliminarla manualmente. La tarea con id '{0}' ha expirado o no se ha encontrado en el servidor <strong>La tarea ha finalizado</strong>. Se va a eliminar automáticamente en <em><abbr data-moment="{0}">{1}</abbr></em> ID Tarea Volver a poner en la cola Fuzzy Volver Generado: {0}ms Hora: Siguiente Anterior Total elementos Elementos por página Parece que la tarea ha sido cancelada No hay tareas en proceso. Iniciado Tareas en proceso No hay tareas en la cola. No se han encontrado tareas en la cola. Prueba a poner alguna. Longitud Próximas tareas Cola Colas Cancelado No se han encontrado tareas recurrentes Cron Última ejecución Próxima ejecución Zona horaria Tareas recurrentes Lanzando... Lanzar ahora Todo va bien - no hay ningún reintento. Reintentos <h4>Los reintentos están funcionando, pero esta página no se puede mostrar</h4> <p> No te preocupes, los reintentos fucionan como deberian, pero tu sistema de almacenamiento de tareas actual no soporta algunas consultas necesarias para mostrar esta página. Actualiza tu sistema de almacenamiento o espera hasta que éste soporte esta funcionalidad </p> <p> Visita la página <a href="{0}">Tareas programadas</a> para ver todas las tareas programadas, incluidos los reintentos. </p> Poner en la cola ahora No hay tareas programadas. Poner en la cola Programadas Tareas programadas No hay ningún servidor activo. Las tareas en segundo plano no serán procesadas. Latido Fuzzy Nombre Colas Iniciada Trabajadores Fuzzy Servidores No se han encontrado tareas completadas Completadas Duración total Tareas completadas En espera Eliminado Con errores Procesando Programadas Completadas Tareas Tareas recurrentes Reintentos Servidores No se puede encontrar el método destino Fuzzy En la cola Sin estado En la cola Conexiones activas Tareas eliminadas Tareas fallidas Tareas en proceso Tareas recurrentes Reintentos Tareas programadas Servidores Tareas completadas Conexiones totales Condición Continuaciones En espera En la cola En la cola / Colas Con errores Completados ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.fa.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 نگران نباشید. کارهای "ادامه دار"، مطابق انتظار در حال اجرا هستند. اما محل ذخیره سازی شما از برخی کوئری های مورد نیاز جهت نمایش این صفحه پشتیبانی نمیکند. لطفاً یا محل ذخیره سازی خود را به روز رسانی کنید یا تا اتمام عملیات منتظر بمانید. کارهای "ادامه دار" در حال اجرا هستند، اما این صفحه قابلیت نمایش ندارد هیچ کاری در وضعیت انتظار برای اجرا وجود ندارد گزینه ها والد کارهای در حال انتظار ایجاد شده حذف شده آیا از حذف تمامی موارد انتخاب شده مطمئن هستید؟ در حال حذف... حذف انتخاب شد وارد کردن کارها به صف در حال ورود به صف دریافت شده شناسه کار کارهای منقضی شده در حالی که داده دریافت میشد، وضعیت کار تغییر کرده جزئیات کمتر جزئیات بیشتر خارج از دسترس روز هفته علت ورود مجدد به صف تلاش مجدد سرویس دهنده وضعیت نامشخص هیچ کار حذف شده ای پیدا نشد حذف شده کارهای حذف شده این کوئری خالی است کارهای وارد شده به صف <strong>کارهای ناموفق منقضی نخواهند شد</strong> تا شما در زمان مناسب مجدداً آنها را اجرا نمایید. باید آنها را بصورت دستی حذف کرده یا دوباره به صف وارد نمایید, یا اینکه اتریبیوت <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> را جهت حذف خودکار آنها اعمال نمایید. در حال حاضر هیچ کار ناموفقی وجود ندارد ناموفق ها کارهای ناموفق صف خالی است کارهای دریافت شده نمودار تاریخچه نمودار لحظه ای داشبورد ایجاد شده آیا از حذف این کار اطمینان دارید؟ وضعیت <strong>کار ناتمام مانده</strong> – توسط سرویس دهنده <code>{0}</code> پردازش شده که اکنون در فهرست <a href="{1}">سرویس دهنده های فعال</a> نیست. این کار بعد از سپری شدن مدت زمانی دوباره اجرا خواهد شد, اما شما میتوانید آن را بصورت دستی حذف یا مجدداً اجرا نمایید.. <strong>بنظر این کار ناتمام مانده</strong> – توسط سرویس دهنده <code>{0}</code>, پردازش شده که بیش از یک دقیقه ی پیش اجرا شده. این کار بعد از سپری شدن مدت زمانی دوباره اجرا خواهد شد, اما شما میتوانید آن را بصورت دستی حذف یا مجدداً اجرا نمایید. کار پس زمینه '{0}' منقضی شد یا در سرور یافت نشد. <strong>این کار به اتمام رسیده</strong>. بصورت خودکار حذف خواهد شد <em><abbr data-moment="{0}">{1}</abbr></em>. شناسه کار ورود مجدد به صف برگشت به سایت ایجاد شد{0}میلی ثانیه زمان: بعدی قبلی کل تعداد در هر صفحه بنظر کار مد نظر شما ناتمام مانده کار هم اکنون در حال اجرا است شروع شده کارهای در حال پردازش هیچ کاری وارد صف نشده هیچ کاری موجود نیست. ابتدا وارد صف کنید طول کار بعدی صف صف ها انصراف داده شده هیچ کار مکرری یافت نشد Cron آخرین اجرا اجرای بعدی منطقه زمانی کارهای مکرر در حال راه اندازی راه اندازی همه چیز صحیح کار میکند- نیاز به اجرای مجدد نیست اجرای مجدد <h4>کارهای نیازمند اجرای مجدد دچار هیچ مشکلی نشده اند, اما این صفحه قابل نمایش نیست</h4> <p> نگران نشوید، کارهای اجرای مجدد مطابق انتظار در حال اجرا هستند اما برخی از کوئری های مورد نیاز جهت نمایش این صفحه توسط سیستم دخیره سازی شما پشتیبانی نمیشوند. . لطفا سیستم ذخیره سازی خود را به روز رسانی کرده یا تا اتمام دستورات منتظر بمانید. </p> <p> لطفا به آدرس <a href="{0}">کارهای زمان بندی شده</a> رجوع کرده تا همه ی کارهای زمان بندی شده از جمله کارهای نیازمند اجرای مجدد را مشاهده نمایید. </p> وارد صف کردن هیچ کار زمانبندی شده ای وجود ندارد وارد کردن به صف زمان بندی شده کارهای زمان بندی شده هیچ سرویس دهنده ی فعالی وجود ندارد. کار های پس زمینه پردازش نخواهند شد. ضربان نام صف شروع شده اجرا کننده ها سرویس دهنده ها هیچ کار موفق شده ای وجود ندارد موفق شده زمان کل اجرا کارهای موفق شده در حال انتظار حذف شده ناموفق در حال پردازش زمانبندی شده موفق شده کارها کارهای مکرر اجرای مجدد سرویس دهنده متد مقصد یافت نشد وارد شده به صف وضعیت خالی است وارد صف شده اتصال های فعال کارهای حذف شده کارهای ناموفق کارهای در حال پردازش کاراهای مکرر اجرای مجدد کارهای زمانبندی شده سرویس دهنده ها کارهای موفق تعداد کل اتصال ها شرط کارهای "ادامه دار" در حال انتظار وارد صف شده وارد صف شده/ صف ها {0}کار(ها)ی ناموفق found. مجدداً اجرا کرده یا بصورت دستی حذف نمایید. ناموفق ها موفقیت آمیز ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.fr.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Ne vous inquiétez pas, les prolongations marchent comme prévu. Votre système de stockage ne supporte pas certaines requêtes nécessaires pour afficher cette page. Merci de mettre à jour votre système de stockage ou d'attendre le support complet de celui-ci. Les prolongations fonctionnent, mais cette page ne peut pas être affichée Aucune tâche en attente trouvée. Options Parent Tâches en attente Créée Supprimer Voulez-vous vraiment SUPPRIMER TOUTES les tâches sélectionnées ? Suppression… Supprimer la sélection Mettre en file d'attente Mise en file d'attente… Récupérées Id Tâche Tâche expirée. L'état de la tâche a changé pendant la récupération des données. Moins de détails… Plus de détails… N/A Jour Semaine Raison Remettre en file d'attente Retenter Serveur État Inconnu Aucune tâche supprimée trouvée. Supprimée Tâches supprimées La file d'attente est vide. Tâches en file d'attente <strong>Les tâches échouées n'expirent pas</strong> pour vous permettre de les remettre en file d'attente sans pression. Vous devriez les remettre en file d'attente ou les supprimer manuellement, ou appliquer l'attribut <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> pour les supprimer automatiquement. Vous n'avez aucune tâche échouée pour le moment. Échouée Tâches échouées La file d'attente est vide. Tâches récupérées Historique Graphique temps réel Aperçu Créée Voulez-vous vraiment supprimer cette tâche ? État <strong>La tâche a été annulée</strong> – elle est en cours de traitement par le serveur <code>{0}</code> qui n'est pas dans la liste des <a href="{1}">serveurs actifs</a> pour le moment. Elle sera retentée automatiquement après un temps d'attente d'invisibilité, mais vous pouvez aussi la remettre en file d'attente ou la supprimer manuellement. <strong>Il semble que la tâche ait été annulée</strong> – elle est en cours de traitement par le serveur <code>{0}</code>, qui a répondu il y a moins d'une minute. Elle sera retentée automatiquement après un temps d'attente d'invisibilité, mais vous pouvez aussi la remettre en file d'attente ou la supprimer manuellement. La tâche '{0}' a expirée ou ne peut pas être trouvée sur le serveur. <strong>La tâche est terminée</strong>. Elle sera retirée automatiquement <em><abbr data-moment="{0}">{1}</abbr></em>. ID Tâche Remettre en file d'attente Retour au site Généré : {0}ms Temps : Suiv. Prec. Total Éléments par page La tâche semble avoir été annulée Aucune tâche en cours actuellement. Démarrée Tâches en cours Aucune tâche en file d'attente. Aucune tâche trouvée en file d'attente. Essayez de mettre une tâche en file d'attente. Longueur Prochaines tâches File d'attente Files d'attente Annulée Aucune tâche récurrente trouvée. Cron Dernière exécution Prochaine exécution Fuseau horaire Tâches récurrentes Déclenchement Déclencher maintenant Tout est OK — aucune nouvelle tentative. Tentatives <h4>Les tentatives fonctionnent, mais cette page ne peut être affichée</h4> <p> Ne vous inquiétez pas, les tentatives fonctionnent comme prévu. Votre système de stockage ne supporte pas certaines requêtes nécessaires pour afficher cette page. Merci de mettre à jour votre système de stockage ou d'attendre le support complet de celui-ci. </p> <p> Allez sur la page <a href="{0}">Tâches programmées</a> pour voir toutes les tâches programmées (y compris les nouvelles tentatives). </p> Mettre en file d'attente maintenant Aucune tâche programmée. Mettre en file d'attente Programmée Tâches programmées Aucun serveur actif. Les tâches de fond ne seront pas exécutées. Pulsation Nom Files d'attente Démarré Unités de travail Serveurs Aucune tâche réussie trouvée. Réussie Durée totale Tâches réussies En attente Supprimées Échouées En cours Programmées Réussies Tâches Tâches récurrentes Nouvelles tentatives Serveurs Impossible de trouver la méthode de destination. En file d'attente Pas d'état En file d'attente Connexions actives Tâches supprimées Tâches échouées Tâches en cours Tâches récurrentes Nouvelles tentatives Tâches programmées Serveurs Tâches réussies Connexions totales Condition Prolongations En attente En file d'attente En file d'attente / Files d'attente {0} tâche(s) supprimé(e)s. Réessayez ou supprimez-les manuellement. Échouée Réussies Désactivée L'expression Cron est invalide ou n'a pas d'occurrence dans les 100 prochaines années. Certains serveurs n'ont pas eu de pulsation dans la dernière minute et pourraient être abandonnés. S'ils ne reportent pas de pulsation dans un futur proche ils seront automatiquement retirés une fois le temps d'attente dépassé, aucune action manuelle n'est requise. Les tâches de fond incomplètes de ces serveurs seront remises en file d'attente automatiquement, mais vous pouvez accélérer le processus en vérifiant la page <a href="{0}">Tâches en cours</a>. Les serveurs abandonnés seront retirés automatiquement Actif Potentiellement abandonné Erreur Arguments ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.nb.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Ikke bekymre deg, fortsettelser fungerer som forventet, men din nåværende jobblagring mangler støtte for noen spørringer som er nødvendige for å vise denne siden. Prøv å oppdatere lagringen eller vent til hele kommandosettet er implementert. Fortsettelser fungerer, men denne siden kan ikke vises Ingen ventende jobber funnet. Alternativer Forelder Ventende Jobber Opprettet Slett Ønsker du virkelig å slette de valgte jobbene? Sletter... Slett valgte Sett jobber i kø Setter i kø... Hentet Id Jobb Jobben har utløpt. Jobbens tilstand har blitt endret under uthenting av data. Færre detaljer... Flere detaljer... N/A Dag Uke Årsak Sett jobber tilbake i kø Kjør på nytt Server Tilstand Ukjent Ingen slettede jobber funnet. Slettet Feil Slettede Jobber Køen er tom. Jobber i kø <strong>Mislykkede jobber utløper ikke</strong> for å la deg å sette dem tilbake i køen uten tidspress. Du bør sette dem tilbake i køen, slette dem manuelt eller bruke <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> -attributtet for å slette dem automatisk. Du har ingen mislykkede jobber for øyeblikket. Mislykket Mislykkede Jobber Køen er tom. Hentede jobber Historisk graf Sanntidsgraf Oversikt Opprettet Ønsker du virkelig å slette denne jobben? Tilstand <strong>Jobben ble avbrutt</strong> – den blir behandlet av server <code>{0}</code> som ikke er i <a href="{1}">aktive servere</a>-listen nå. Den blir hentet automatisk etter usynlighetstid, men du kan også sette den tilbake i køen eller slette den manuelt. <strong>Ser ut som jobben ble avbrutt</strong> - den blir behandlet av server <code>{0}</code> som rapporterte om livstegn for over ett minutt siden. Den vil bli hentet automatisk etter usynlighetstid, men du kan også sette den tilbake i køen eller slette den manuelt. Bakgrunnsjobben '{0}' har utløpt eller finnes ikke på serveren. <strong>Jobben er utført</strong>. Den vil bli fjernet automatisk <em><abbr data-moment="{0}">{1}</abbr></em>. Jobb ID Sett tilbake i kø Tilbake til siden Generert på {0}ms Lagringstid: Applikasjonstiden er ikke synkronisert med lagringstiden Tidspunkt: Neste Forrige Totalt antall Antall per side Det ser ut som at jobben ble avbrutt Ingen jobber blir behandlet akkurat nå. Startet Jobber til Behandling Ingen jobber har bltt lagt i kø. Ingen jobber har blitt lagt i køen. Prøv å sett en jobb i kø. Lengde Neste jobber Køer Avbrutt Ingen gjentakende jobber funnet. Cron Siste kjøring Neste kjøring Tidssone Gjentakende jobber Kjører... Kjør nå Alt er OK – ingen jobber har blitt gjenforsøkt. Gjenforsøk <h4>Gjenforsøk virker, men denne siden kan ikke vises</h4> <p> Ikke bekymre deg, gjenforsøk av jobber fungerer som forventet. Din nåværende jobblagring mangler støtte for noen spørringer som er nødvendig for å vise denne siden. Prøv å oppdatere lagringen eller vent til hele kommandosettet er implementert. </p> <p> Vennligst gå til <a href="{0}">Planlagte Jobber</a> for å se alle planlagte jobber, inkludert gjenforsøk. </p> Sett i kø nå Det er ingen planlagte jobber. Sett i kø Planlagt Planlagte Jobber Det er ingen aktive servere. Bakgrunnsjobber vil ikke bli behandlet. Siste livstegn Navn Køer Startet Arbeidere Servere Ingen vellykkede jobber funnet. Vellykkede Total Varighet Ventetid Varighet Vellykkede Jobber Venter Slettet Mislykket Til Behandling Planlagt Vellykkede Jobber Gjentakende Jobber Gjenforsøk Servere Kan ikke finne målmetoden. Satt i kø Ingen tilstand Satt i kø Antall aktive tilkoblinger Slettede Jobber Mislykkede Jobber Jobber til Behandling Gjentakende Jobber Gjenforsøk Planlagte Jobber Servere Vellykkede Jobber Totalt antall tilkoblinger Betingelse Fortsettelser Venter Satt i kø Satt i kø / Køer {0} mislykkede jobber funnet. Sett dem tilbake i køen eller slett dem manuelt. Mislykket Vellykkede Deaktivert Cron-uttrykket er ugyldig eller har ingen forekomster i løpet av de neste 100 årene Noen av serverne har ikke rapportert livstegn i løpet av det siste minuttet og kan bli avbrutt. Hvis ikke de rapporterer livstegn i nær fremtid, vil de automatisk bli fjernet etter et tidsavbrudd. Det kreves ingen manuell handling. Ufullstendige bakgrunnsjobber som kjører på disse serverne vil bli satt i kø på nytt automatisk, men du kan fremskynde prosessen ved å sjekke <a href="{0}">Jobber til Behandling</a>. Avbrutte servere fjernes automatisk Aktive Muligens avbrutt Feil Parametere Skjemaversjon Siden Aktive transaksjoner Datafil(er) brukt (MB) Loggfil(er) brukt (MB) ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.nl.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Geen paniek, voortzettingen werken zoals verwacht. De huidige opslagmethode voor taken ondersteund niet alle functies die nodig zijn om deze pagina te laten zien. Werk de opslagmethode bij, of wacht tot een complete implementatie gereed is. Voortzettingen werken zoals verwacht, maar de pagina kan niet worden getoond. Geen afwachtende taken gevonden. Opties Bovenliggende Wachtende taken Aangemaakt Verwijderen Weet u zeker dat u alle geselecteeerde taken wilt VERWIJDEREN? Verwijderen... Verwijder selectie Taken inplannen Inplannen... Opgehaald Id Taak Taak verlopen. De status van de taak is gewijzigd tijdens het ophalen. Minder details... Meer details... n.v.t. Dag Week Reden Taken opnieuw inplannen Opnieuw Server Status Onbekend Geen verwijderde taken gevonden. Verwijderd Verwijderde taken De wachtrij is leeg. Ingeplande taken <strong>Mislukte taken verlopen niet</strong> zodat ze opnieuw ingepland kunnen worden zonder tijdsdruk. Deze taken dienen handmatig opnieuw ingepland of gewist te worden, of het attribuut <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> kan worden toegevoegd in code om deze taken automatisch te wissen. Geen mislukte taken gevonden. Mislukt Mislukte taken De lijst met opgehaalde taken is leeg. Opgehaalde taken Historische grafiek Realtime grafiek Overzicht Aangemaakt Wilt u deze taak echt verwijderen? Status <strong>De taak is afgebroken</strong> – de taak is verwerkt door server <code>{0}</code> welke op dit moment niet voorkomt in de lijst met <a href="{1}">actieve servers</a>. De taak wordt automatisch herstart na de time-out die is ingesteld voor niet zichtbare taken, maar de taak kan ook handmatig opnieuw worden gestart, of verwijderd. <strong>Het lijkt erop dat de taak is afgebroken</strong> – de taak is verwerkt door server <code>{0}</code>, welke meer dan 1 minuut geleden voor het laatst activiteit heeft gemeld. De taak wordt automatisch herstart na de time-out die is ingesteld voor niet zichtbare taken, maar de taak kan ook handmatig opnieuw worden gestart, of verwijderd. Taak '{0}' is verlopen, of kan niet worden gevonden op de server. <strong>De taak is afgerond</strong>. De taak zal automatisch worden verwijderd op <em><abbr data-moment="{0}">{1}</abbr></em>. Taak Id Opnieuw inplannen Terug naar de site Gegenereerd: {0}ms Tijd: Volgende Vorige Aantal items Items per pagina Het lijkt erop dat de taak is afgebroken Op dit moment worden er geen taken uitgevoerd. Gestart Uitvoerende taken Geen taken in de wachtrij. Geen taken in de wachtrij gevonden. Probeer een taak uit te voeren. Lengte Volgende taken Wachtrij Wachtrijen Geannuleerd Geen herhalende taken gevonden. Cron Laatst uitgevoerd Volgende keer Tijdzone Herhalende taken Uitvoeren... Nu uitvoeren Alles in orde – er zijn geen taken om opnieuw te proberen. Opnieuw proberen <h4>Opnieuw proberen werkt, maar de pagina kan niet worden getoond</h4> <p> Geen paniek, taken worden wel opnieuw geprobeerd. De huidige opslagmethode voor taken ondersteund niet alle functies die nodig zijn om deze pagina te laten zien. Werk de opslagmethode bij, of wacht tot een complete implementatie gereed is. </p> <p> Voor een overzicht van alle ingeplande taken, inclusief taken die opnieuw geprobeerd worden, ga naar de <a href="{0}">Ingeplande taken</a>. </p> Nu in wachtrij plaatsen Er zijn geen ingeplande taken. In wachtrij Ingepland Ingeplande taken Er zijn geen actieve servers. Achtergrondtaken worden niet uitgevoerd. Hartslag Naam Wachtrijen Gestart Werkprocessen Servers Geen geslaagde taken gevonden. Geslaagd Totale duur Geslaagde taken In afwachting Verwijderd Mislukt Verwerken Ingepland Geslaagd Taken Herhalende taken Opnieuw proberen Servers Kan de doelmethode niet vinden. Ingepland Geen status In wachtrij Actieve verbindingen Verwijderde taken Mislukte taken Uitvoerende taken Herhalende taken Opnieuw proberen Ingeplande taken Servers Geslaagde taken Totaal aantal verbindingen Voorwaarde Voorzettingen In afwachting In wachtrij In wachtrij/Wachtrijen {0} mislukte taken gevonden. Herstart of verwijder deze handmatig. Mislukt Geslaagd Uitgeschakeld Cron expressie is ongeldig, of bevat geen gebeurtenis in de komende 100 jaar Sommige servers hebben geen hartslag gerapporteerd in de afgelopen minuut, en kunnen zijn gestopt. Als er niet spoedig een hartslag wordt gerapporteerd, worden ze automatisch verwijderd. Een handmatige actie is niet nodig. Niet afgeronde taken die op deze servers worden uitgevoerd zullen automatisch opnieuw in de wachtrij worden geplaatst, maar dit proces kan worden versneld op de <a href="{0}">Uitvoerende taken</a> pagina. Gestopte servers worden automatisch verwijderd Actief Mogelijk afgebroken ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.pt-BR.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 As sequências estão a funcionar conforme o esperado. O sistema atual de armazenamento não permite algumas consultas necessárias à apresentação da página. Altere o sistema de armazenamento ou aguarde até que sejam implementadas. Continuação funciona, mas esta página não pode ser exibida. Nenhuma tarefa pendente encontrada. Opções Pai Fuzzy Tarefas em espera Criado Remover Tem certeza de que deseja REMOVER TODAS as tarefas selecionadas? Removendo... Remover registros selecionados Colocar tarefa na fila Colocando na fila... Obtidos Fuzzy Id Tarefa Tarefa expirada O status da tarefa foi alterado enquanto as informações estavam sendo obtidas. Fuzzy Menos detalhes... Mais detalhes... Indisponível Dia Semana Motivo Reagendar tarefas Fuzzy Tentar novamente Servidor Estado Desconhecido Nenhuma tarefa removida encontrada. Removidas Tarefas removidas A fila está vazia. Tarefas enfileiradas <strong>As tarefas em falha não expiram</strong> para permitir que sejam colocadas novamente em fila a qualquer momento. Deve recolocá-las em fila, removê-las manualmente ou definir o atributo <code> AutomaticRetry (OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> para removê-las automaticamente. Não existem tarefas em falha neste momento. Falhas Tarefas em falha A fila está vazia. Tarefas obtidas Fuzzy Gráfico histórico Gráfico em tempo real Painel de controle Fuzzy Criado Tem certeza de que deseja remover esta tarefa? Estado <strong>A tarefa foi cancelada</strong> – processada pelo servidor <code>{0}</code> que não está na lista <a href="{1}"> de servidores ativos</a> neste momento. Será repetida automaticamente depois de um intervalo de invisibilidade, no entanto pode ser recolocá-la em fila ou removê-la manualmente. <strong>A tarefa foi abortada</strong> - foi processada pelo servidor <code>{0}</code> que relatou uma pulsação mais de um minuto atrás. Ela será recuperado automaticamente após o período de invisibilidade, mas você poderá colocá-lo novamente na fila ou excluí-lo manualmente. A tarefa com o Id '{0}' expirou ou não foi encontrada no servidor. <strong>A tarefa terminou</strong>. Será removida automaticamente em <em><abbr data-moment="{0}">{1}</abbr></em>. Id da Tarefa Colocar novamente na fila Fuzzy Voltar Gerado: {0} ms Hora: Próxima Anterior Total registros Registros por página A tarefa foi aparentemente cancelada Nenhuma tarefa em processamento. Iniciadas Tarefas em processamento Sem tarefas na fila. Sem tarefas na fila. Coloque uma tarefa em fila. Quantidade Próximas tarefas Fila Filas Cancelado Não foram encontradas tarefas recorrentes Cron Última execução Próxima execução Fuso horário Tarefas recorrentes Acionando... Executar agora Tudo OK – não há reagendamentos. Reagendamentos <h4>O reagendamento está a funcionar, mas esta página não consegue ser apresentada</h4> <p> O reagendamento está a funcionar como esperado. O sistema de armazenamento atual não permite algumas consultas necessárias à apresentação da página. Altere o sistema de armazenamento ou aguarde que as funcionalidades sejam implementadas. </p> <p> Acesse <a href="{0}">Tarefas agendadas</a> para ver todas as tarefas agendadas e reagendadas. </p> Colocar em fila agora Sem tarefas agendadas. Reagendar Agendado Tarefas Agendadas Não existem servidores activos. As tarefas não serão executadas. Sinal Fuzzy Nome Filas Iniciadas Serviços de execução Fuzzy Servidores Nenhuma tarefa concluída foi encontrada. Concluídas Duração total Tarefas concluídas Em espera Removidas Em falha Processando Agendadas Concluídas Tarefas Tarefas Recorrentes Reagendamentos Servidores Não foi possível encontrar o método alvo. Fuzzy Enfileiradas Sem estado Enfileiradas Conexões ativas Tarefas Removidas Tarefas em falha Tarefas em processamento Tarefas recorrentes Reagendamentos Tarefas Agendadas Servidores Tarefas com sucesso Total de conexões Condição Continuações Em espera Na fila Enfileiradas / Filas Com falha Concluídos Exceção Tempo de armazenamento: A hora da aplicação está dessincronizada com a hora do sistema de armazenamento Latência Duração {0} tarefa(s) com erro encontradas. Reagendar ou remover manualmente. Inativo Parâmetros Versão Schema Desde Transações Ativas Arquivo(s) Dados Usados (MB) Arquivo(s) Log Usados (MB) ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.pt-PT.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 As sequências estão a funcionar conforme o esperado. O sistema atual de armazenamento não permite algumas consultas necessárias à apresentação da página. Altere o sistema de armazenamento ou aguarde até que sejam implementadas. As sequências estão a funcionar, não é possivel apresentar a página Nenhuma tarefa pendente encontrada. Opções Pai Tarefas em espera Criado Remover Tem certeza de que deseja REMOVER TODAS as tarefas selecionadas? Removendo... Remover registos seleccionados Colocar tarefas em fila A colocar em fila... Obtidos Id Tarefa Tarefa expirada. O estado da tarefa foi alterado enquanto os dados estavam sendo obtidos. Menos detalhes... Mais detalhes... Indisponível Dia Semana Motivo Reagendar tarefas Tentar novamente Servidor Estado Desconhecido Nenhuma tarefa removida encontrada. Removidas Tarefas removidas A fila está vazia. Tarefas em fila <strong>As tarefas com erros não expiram</strong> para permitir que sejam colocadas novamente em fila a qualquer momento. Deve recolocá-las em fila, removê-las manualmente ou definir o atributo <code> AutomaticRetry (OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> para removê-las automaticamente. Não existem tarefas com erro neste momento. Erros Tarefas com erro A fila está vazia. Tarefas obtidas Gráfico histórico Gráfico em tempo real Painel de controlo Criado Tem certeza de que deseja remover esta tarefa? Estado <strong>A tarefa foi cancelada</strong> – processada pelo servidor <code>{0}</code> que não está na lista <a href="{1}"> de servidores ativos</a> neste momento. Será repetida automaticamente depois de um intervalo de invisibilidade, no entanto pode ser recolocá-la em fila ou removê-la manualmente. <strong>A tarefa foi aparentemente cancelada</strong> – processada pelo servidor <code>{0}</code>, que reportou um sinal há mais de 1 minute. O servidor será restaurado automáticamente após um tempo de invisibilidade, no entanto pode ser recolocado em fila ou removido manualmente. A tarefa com o Id '{0}' expirou ou não foi encontrada no servidor. <strong>A tarefa terminou</strong>. Será removida automaticamente em <em><abbr data-moment="{0}">{1}</abbr></em>. Id da Tarefa Colocar novamente na fila Voltar Gerado: {0}ms Hora: Próxima Anterior Total registos Registos por página A tarefa foi aparentamente cancelada Nenhuma tarefa em processamento. Iniciadas Tarefas em processamento Sem tarefas em fila. Sem tarefas na fila. Coloque uma tarefa em fila. Quantidade Próximas tarefas Fila Filas Cancelado Não foram encontradas tarefas recorrentes. Cron Última execução Próxima execução Fuso horário Tarefas recorrentes Acionando... Executar agora Tudo OK – não há reagendamentos. Reagendamentos <h4>O reagendamento está a funcionar, mas esta página não consegue ser apresentada</h4> <p> O reagendamento está a funcionar como esperado. O sistema de armazenamento atual não permite algumas consultas necessárias à apresentação da página. Altere o sistema de armazenamento ou aguarde que as funcionalidades sejam implementadas. </p> <p> Aceda a <a href="{0}">Tarefas agendadas</a> para ver todas as tarefas agendadas e reagendadas. </p> Colocar em fila agora Sem tarefas agendadas. Reagendar Agendado Tarefas Agendadas Não existem servidores activos. As tarefas não serão executadas. Sinal Nome Filas Iniciadas Workers Servidores Nenhuma tarefa concluída encontrada. Concluídas Duração Tarefas concluídas Em espera Removidas Com Erro Processando Agendadas Concluídas Tarefas Tarefas Recorrentes Reagendamentos Servidores Não foi possível encontrar o método alvo. Em Fila Sem estado Em Fila Ligações activas Tarefas Removidas Tarefas com erro Tarefas em processamento Tarefas recorrentes Reagendamentos Tarefas Agendadas Servidores Tarefas com sucesso Total de Ligações Condição Sequências Em espera Em fila Em fila / Filas {0} tarefa(s) com erro encontradas. Reagendar ou remover manualmente. Com erro Com sucesso Inactivo A expressão Cron é inválida ou não tem nenhuma ocorência nos próximos 100 anos Alguns servidores não reportaram nehum sinal no último minuto e podem ser cancelados. Se não reportarem nenhum sinal brevemente serão cancelados assim que o tempo limite seja ultrapassado, não é necessária nenhuma acção manual. Tarefas incompletas nesses servidores serão repetidas automaticamente, no entanto o processo pode ser acelarado acedendo e verificando a seguinte página <a href="{0}">Tarefas em Processamento</a>. Servidores cancelados serão removidos automaticamente Activo Possivelmente cancelados Erro Exceção Tempo de armazenamento: A hora da aplicação está dessincronizada com a hora do sistema de armazenamento Latência Duração Parâmetros Versão Schema Desde Transações Ativas Ficheiro(s) Dados Usadas (MB) Ficheiro(s) Log Usados (MB) ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.pt.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 As sequências estão a funcionar conforme o esperado. O sistema atual de armazenamento não permite algumas consultas necessárias à apresentação da página. Altere o sistema de armazenamento ou aguarde até que sejam implementadas. As sequências estão a funcionar, mas não é possivel apresentar a página Nenhuma tarefa pendente encontrada. Opções Pai Tarefas em espera Criado Remover Tem certeza de que deseja REMOVER TODAS as tarefas selecionadas? Removendo... Remover registos seleccionados Colocar tarefas em fila A colocar em fila... Obtidos Id Tarefa Tarefa expirada. O estado da tarefa foi alterado enquanto os dados estavam sendo obtidos. Menos detalhes... Mais detalhes... Indisponível Dia Semana Motivo Reagendar tarefas Tentar novamente Servidor Estado Desconhecido Nenhuma tarefa removida encontrada. Removidas Tarefas removidas A fila está vazia. Tarefas em fila <strong>As tarefas com erros não expiram</strong> para permitir que sejam colocadas novamente em fila a qualquer momento. Deve recolocá-las em fila, removê-las manualmente ou definir o atributo <code> AutomaticRetry (OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> para removê-las automaticamente. Não existem tarefas com erro neste momento. Erros Tarefas com erro A fila está vazia. Tarefas obtidas Gráfico histórico Gráfico em tempo real Painel de controlo Criado Tem certeza de que deseja remover esta tarefa? Estado <strong>A tarefa foi cancelada</strong> – processada pelo servidor <code>{0}</code> que não está na lista <a href="{1}"> de servidores ativos</a> neste momento. Será repetida automaticamente depois de um intervalo de invisibilidade, no entanto pode ser recolocá-la em fila ou removê-la manualmente. <strong>A tarefa foi aparentemente cancelada</strong> – processada pelo servidor <code>{0}</code>, que reportou um sinal há mais de 1 minute. O servidor será restaurado automáticamente após um tempo de invisibilidade, no entanto pode ser recolocado em fila ou removido manualmente. A tarefa com o Id '{0}' expirou ou não foi encontrada no servidor. <strong>A tarefa terminou</strong>. Será removida automaticamente em <em><abbr data-moment="{0}">{1}</abbr></em>. Id da Tarefa Colocar novamente na fila Voltar Gerado: {0}ms Hora: Próxima Anterior Total registos Registos por página A tarefa foi aparentamente cancelada Nenhuma tarefa em processamento. Iniciadas Tarefas em processamento Sem tarefas na fila. Sem tarefas na fila. Coloque uma tarefa em fila. Quantidade Próximas tarefas Fila Filas Cancelado Não foram encontradas tarefas recorrentes. Cron Última execução Próxima execução Fuso horário Tarefas recorrentes Acionando... Executar agora Tudo OK – não há reagendamentos. Reagendamentos <h4>O reagendamento está a funcionar, mas esta página não consegue ser apresentada</h4> <p> O reagendamento está a funcionar como esperado. O sistema de armazenamento atual não permite algumas consultas necessárias à apresentação da página. Altere o sistema de armazenamento ou aguarde que as funcionalidades sejam implementadas. </p> <p> Aceda a <a href="{0}">Tarefas agendadas</a> para ver todas as tarefas agendadas e reagendadas. </p> Colocar em fila agora Sem tarefas agendadas. Reagendar Agendado Tarefas Agendadas Não existem servidores activos. As tarefas não serão executadas. Sinal Nome Filas Iniciadas Workers Servidores Nenhuma tarefa concluída encontrada. Concluídas Duração Tarefas concluídas Em espera Removidas Com Erro Processando Agendadas Concluídas Tarefas Tarefas Recorrentes Reagendamentos Servidores Não foi possível encontrar o método alvo. Em Fila Sem estado Em Fila Ligações activas Tarefas Removidas Tarefas com erro Tarefas em processamento Tarefas recorrentes Reagendamentos Tarefas Agendadas Servidores Tarefas com sucesso Total de Ligações Condição Sequências Em espera Em fila Em fila / Filas {0} tarefa(s) com erro encontradas. Reagendar ou remover manualmente. Com erro Com sucesso Inactivo A expressão Cron é inválida ou não tem nenhuma ocorência nos próximos 100 anos Alguns servidores não reportaram nehum sinal no último minuto e podem ser cancelados. Se não reportarem nenhum sinal brevemente serão cancelados assim que o tempo limite seja ultrapassado, não é necessária nenhuma acção manual. Tarefas incompletas nesses servidores serão repetidas automaticamente, no entanto o processo pode ser acelarado acedendo e verificando a seguinte página <a href="{0}">Tarefas em Processamento</a>. Servidores cancelados serão removidos automaticamente Activo Possivelmente cancelados Erro Exceção Tempo de armazenamento: A hora da aplicação está dessincronizada com a hora do sistema de armazenamento Latência Duração Parâmetros Versão Schema Desde Transações Ativas Ficheiro(s) Dados Usadas (MB) Ficheiro(s) Log Usados (MB) ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Don't worry, continuations are working as expected. But your current job storage does not support some queries required to show this page. Please try to update your storage or wait until the full command set is implemented. Continuations are working, but this page can't be displayed No jobs found in awaiting state. Options Parent Awaiting Jobs Created Delete Do you really want to DELETE ALL selected jobs? Deleting... Delete selected Enqueue jobs Enqueueing... Fetched Id Job Job expired. Job's state has been changed while fetching data. Fewer details... More details... N/A Day Week Reason Requeue jobs Retry Server State Unknown No deleted jobs found. Deleted Exception Deleted Jobs The queue is empty. Enqueued Jobs <strong>Failed jobs do not become expired</strong> to allow you to re-queue them without any time pressure. You should re-queue or delete them manually, or apply <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> attribute to delete them automatically. You have no failed jobs at the moment. Failed Failed Jobs The queue is empty. Fetched Jobs History Graph Realtime Graph Overview Created Do you really want to delete this job? State <strong>The job was aborted</strong> – it is processed by server <code>{0}</code> which is not in the <a href="{1}">active servers</a> list for now. It will be retried automatically after invisibility timeout, but you can also re-queue or delete it manually. <strong>Looks like the job was aborted</strong> – it is processed by server <code>{0}</code>, which reported its heartbeat more than 1 minute ago. It will be retried automatically after invisibility timeout, but you can also re-queue or delete it manually. Background job '{0}' has expired or could not be found on the server. <strong>The job is finished</strong>. It will be removed automatically <em><abbr data-moment="{0}">{1}</abbr></em>. Id Requeue Back to site Generated: {0}ms Storage Time: Application time is out of sync with storage time Application Time: Next Prev Total items Items per page Looks like the job was aborted No jobs are being processed right now. Started Processing Jobs No jobs queued. No queued jobs found. Try to enqueue a job. Length Next jobs Queue Queues Canceled No recurring jobs found. Cron Last execution Next execution Time zone Recurring Jobs Triggering... Trigger now All is OK – you have no retries. Retries <h4>Retries are working, but this page can't be displayed</h4> <p> Don't worry, retries are working as expected. Your current job storage does not support some queries required to show this page. Please try to update your storage or wait until the full command set is implemented. </p> <p> Please go to the <a href="{0}">Scheduled jobs</a> page to see all the scheduled jobs including retries. </p> Enqueue now There are no scheduled jobs. Enqueue Scheduled Scheduled Jobs There are no active servers. Background tasks will not be processed. Heartbeat Name Queues Started Workers Servers No succeeded jobs found. Succeeded Total Duration Latency Duration Succeeded Jobs Awaiting Deleted Failed Processing Scheduled Succeeded Jobs Recurring Jobs Retries Servers Can not find the target method. Enqueued No state Enqueued Active Connections Deleted Jobs Failed Jobs Processing Jobs Recurring Jobs Retries Scheduled Jobs Servers Succeeded Jobs Total Connections Condition Continuations Awaiting Enqueued Enqueued / Queues {0} failed job(s) found. Retry or delete them manually. Failed Succeeded Disabled Cron expression is invalid or don't have any occurrences over the next 100 years Some of the servers don't have heartbeat reported within the last minute and may be aborted. If they don't report heartbeat in the near future, they will be removed automatically after timeout is exceeded, no manual action is required. Incomplete background jobs running on those servers will be re-queued automatically, but you can speed up the process by checking the <a href="{0}">Processing Jobs</a> page. Aborted servers will be removed automatically Active Possibly aborted Error Parameters Schema Version Since Active Transactions Data File(s) Used (MB) Log File(s) Used (MB) ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.sv.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Oroa dig inte, fortsättningar fungerar som dom ska. Din nuvarande lagring för jobb stödjer inte vissa frågor som behövs för att visa den här sidan. Uppdatera till en annan lagringstyp eller vänta och hoppas att den nuvarande får stöd för alla frågetyper. Fortsättningar fungerar, men den här sidan kunde inte visas Inga jobb hittades i väntande tillstånd. Inställningar Förälder Sen Väntade jobb Skapad Ta bort Vill du verkligen TA BORT ALLA valda jobb? Tar bort... Ta bort valda Köa upp jobb Lägger till i kö... Hämtad Id Jobb Utgånget jobb. Jobbets tillstånd ändrades medan informationen hämtades. Färre detaljer... Mer detaljer... N/A Dag Vecka Orsak Köa om jobb Försök igen Server Tillstånd Okänd Inga borttagna jobb hittades. Borttaget Undantag Borttagna jobb Kön är tom. Köade jobb <strong>Misslyckade jobb löper inte ut</strong> så att man inte ska känna sig stressad att köa upp det igen. Köa upp eller ta bort dom manuellt, eller sätt följande attribut i koden för att radera dom automatiskt: <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> Du har inga misslyckade jobb just nu. Misslyckad Misslyckade jobb Kön är tom. Hämtade jobb Historisk graf Realtidsgraf Översikt Skapad Vill du verkligen ta bort det här jobbet? Tillstånd <strong>Jobbet avbröts</strong> – det processades av servern <code>{0}</code> som just nu inte är med på listan över <a href="{1}">aktiva servrar</a>. Det kommer att automatiskt försökas igen efter "invisibility timeout", men du kan också köa upp eller ta bort det manuellt. <strong>Det verkar som att jobbeet avbröts</strong> – det processas av servern <code>{0}</code>, som gav sitt senaste livstecken för mer än en minut sedan. Det kommer att automatiskt försökas igen efter "invisibility timeout", men du kan också köa upp eller ta bort det manuellt. Bakgrundsjobbet '{0}' har löpt ut eller kunde inte hittas på servern. <strong>Jobbet är färdigt</strong>. Det kommer att tas bort automatiskt <em><abbr data-moment="{0}">{1}</abbr></em>. Jobb ID Köa igen Tillbaka till webbplats Genererades på: {0}ms Lagringstid: Applikationstid: Applikationstiden är inte i synk med lagringstiden Nästa Föregående Totalt antal Antal per sida Det verkar som att jobbet avbröts Inga jobb körs just nu. Startade Processing Jobs Inga köade jobb. Inga köade jobb finns. Försök med att köa upp ett jobb. Längd Nästa jobb Köer Avbrutet Inga återkommande jobb hittades. Cron Senaste körning Nästa körning Tidszon Återkommande jobb Startar... Starta nu Allt är OK – du har inga nya försök. Nya försök <h4>Nya försök fungerar, men den här sidan kan inte visas</h4> <p> Oroa dig inte, nya försök fungerar som dom ska. Din nuvarande lagring för jobb stödjer inte vissa frågor som behövs för att visa den här sidan. Uppdatera till en annan lagringstyp eller vänta och hoppas att den nuvarande får stöd för alla frågetyper. </p> <p> Gå till <a href="{0}">Schemalagda jobb</a>-sidan för att se alla schemalagda jobb inklusive nya försök. </p> Köa upp nu Det finns inga schemalagda jobb. Köa upp Schemalagd Schemalagda jobb Det finns inga aktiva servrar. Bakgrundsjobb kommer inte att köras. Livstecken Namn Köer Startade Arbetare Servrar Inga lyckade jobb hittades. Tidsåtgång Fördröjning Lyckades Total tidsåtgång Lyckade jobb Väntar Borttagna Misslyckade Kör Schemalagd Lyckade Jobb Återkommande jobb Antal nya försök Servrar Kan inte hitta målmetoden. Köad Inget tillstånd Köade Aktiva anslutningar Borttagna jobb Misslyckade jobb Pågående jobb Återkommande jobb Antal nya försök Aktiva transaktioner Datafiler (MB) Loggfiler (MB) Schemaversion Schemalagda jobb Servrar Lyckade jobb Antal anslutningar Tillstånd Fortsättningar Väntande Köade Köade / Kö {0} misslyckade jobb hittades. Försök igen eller ta bort dom manuellt. Misslyckade Lyckade Avtängd Cron-uttrycket är ogiligt eller inträffar inte dom närmaste hundra åren Vissa servrar har inte gett några livstecken den senaste minuten och kan komma att avbrytas. Om dom inte ger några livstecken inom en snar framtid så kommer dom att tas bort automatiskt efter en timeout. Pågående bakgundsjobb på dessa servrar kommer att köas upp igen automatiskt, men du kan snabba upp processen genom att kika på sidan <a href="{0}">Pågående jobb</a>. Avbrutna servrar kommer att tas bort automatiskt Aktiv Möjligen avbrutna Fel Parametrar ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.tr-TR.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Endişelenmeyin, continuations beklendiği gibi çalışıyor. Ancak mevcut iş kaynaklarınız bu sayfayı göstermek için gereken bazı sorguları desteklemiyor. Lütfen iş kaynaklarınızı güncellemeyi deneyin veya tüm entegrasyon tamamlana kadar bekleyin. Continuations işleriniz çalışıyor ancak sayfa gösterilemiyor. Bekleyen bir işiniz bulunmamaktadır. Ayarlar Üst Bekleyen İşler Oluşturan Sil TÜM SEÇİLİ işleri silmek istediğine emin misin? Siliniyor... Seçilileri Sil Kuyruklu işler Kuyruğa alınıyor... Getirildi Kimlik İş İşin süresi doldu. Veriler alınırken işin durumu değiştirildi. Daha az detay... Detay fazla detay... Yok Gün Hafta Neden İşleri yeniden sıraya al Tekrar Sunucu Durum Bilinmeyen Silinen işler bulunamadı. Silindi Silinen İşler Kuyruk boş. Sıralanmış İşler <strong>Başarısız olan işlerin süresi dolmaz.</strong> Bu işleri yeniden sıraya koymanız için zamanı iyi ayarlamalısınız. Bu işleri manuel olarak tekrar kuyruğa alabilir veya silebilirsiniz. İpucu : <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> attribute'unu ekleyerek otomatik olarak silebilrsiniz. Şu anda Başarısız iş bulunmamaktadır. Başarısız Başarısız İşler Kuyruk boş İş Sonuçları Geçmiş Grafiği Canlı Grafik Genel Bakış Oluşturulan Bu işi gerçekten silmek istediğine emin misin? Durum <strong>Bu iş iptal edildi</strong> – Sunucu tarafından işlendi. <code>{0}</code> which is not in the <a href="{1}">active servers</a> list for now. Zaman aşımı sonrası tekrardan yenilenecektir, ancak manuel olarakta tekrar kuyruğa alabilir veya silebilirsiniz. <strong>İş iptal edilmiş görüntüleniyor.</strong> – Sunucu tarafından işlendi. <code>{0}</code>, tarafından kontrol zamanın 1 dakikadan fazla olduğunu raporladı. Zaman aşımı sonrası tekrardan yenilenecektir, ancak manuel olarakta tekrar kuyruğa alabilir veya silebilirsiniz. Arkaplan işi '{0}' zaman aşımına uğradı veya sunucu tarafından bulunamadı. <strong>İş tamamlandı</strong>. Otomatik olarak silinecektir. <em><abbr data-moment="{0}">{1}</abbr></em>. İş Kimliği Yeniden sıraya al Siteye geri dön Oluşturuldu: {0}ms Zaman: İleri Geri Toplam eleman Görüntülenme adeti İş iptal edildi Şuan hiç bir iş işlenemiyor. Başlatıldı. Çalışan İşler Sıraya alınmış iş yok. Kuyruğa alınmış iş bulunamadı. Bir işi kuyruğa almaya çalışın. Bekleyenler Sonraki işler Kuyruk Kuyruklar İptal edildi Tekrarlayan iş bulunamadı. Cron Son Yürütme Sonraki yürütme Saat dilimi Tekrarlayan İşler Tetikleniyor... Şimdi tetikle Her şey tamamlandı. Tekrarlayan iş bulunmamaktadır. Tekrarlayan İşler <h4>Tekrarlayan işler çalışıyor, ancak sayfa gösterilemiyor</h4> <p> Merak etmeyin, yeniden denemeler beklendiği gibi çalışıyor. Mevcuttaki iş kaynağı bazı sorguları göstermek için desteklemiyor. Lütfen kaynağı güncelleyin veya tüm komut seti entegre edilene kadar bekleyin. </p> <p> Lütfen <a href="{0}">Planlanmış işler</a> sayfasına gidip planmış ve yeniden tetiklenmiş işleri görüntüleyiniz. </p> Sıraya al Planlanmış bir iş bulunmamakta. Sıraya al Planlanmış Planlanmış İşler Aktif sunucu bulunamadı Arkaplan işleri işlenemiyor. Kontrol Zamanı İsim Kuyruklar Başlatıldı Çalışan Servisler Sunucular Tamamlanmış iş bulunamadı. Başarılı Toplam Süre Başarılı işler Beklenenler Silinenler Başarısızlar İşlenenler Planlananlar Başarılılar İşler Tekrarlayan İşler Yeniden Denenenler Sunucular Çalıştırılacak method bulunamadı. Kuyruğa alındı Durum yok Sıradakiler Aktif Bağlantılar Silinen İşler Başarısız İşler İşlenen İşler Tekrarlayan İşler Yeniden Denenenler Planlanan İşler Sunucular Başarılı İşler Toplam Bağlantı Durum Sürekli Bekliyor Kuyruğa alındı Kuyruğa Alınan {0} başarısız iş bulundu. Tekrar deneyin veya manuel silin. Başarısız Başarılı Etkisiz Cron ifadesi geçersiz veya 100 yıldan büyük bir ifade içermektedir. Bazı sunuculardan cevap alınamadığı gözlenlendi. Zaman aşımı içinde cevap vermedikleri taktirde, otomatik olarak silineceklerdir. Bu işlem için herhangi bir müdaheleye gerek yoktur. Tamamlanmayan arkaplan işleri bu sunucularda çalışmaya devam edecektir. Hızlıca <a href="{0}">İşlenen İşler</a> sayfasından ilgili işleri kontrol edebilirsiniz. İptal edilen sunucular otomatik olarak kaldırılacaktır. Aktif İptal edilmiş. Hata Parametreler ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.zh-TW.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 別擔心,持續性工作正在按預期執行。 但是,您當前的作業存儲空間不支持顯示此頁面所需的一些查詢。 請嘗試更新您的存儲空間,或者等到完整的命令集被執行。 工作繼續執行中,但是無法顯示該頁面 沒有等待中的工作 選項 父層 等待中的工作 建立 刪除 您確定要刪除所選的全部工作嗎? 刪除中… 刪除選取 佇列工作 加入佇列中… 獲取到 編號 工作 工作過期 工作狀態已被變更 收起… 更多… N/A 原因 重新加入佇列 重試 伺服器 狀態 未知 沒有找到任何已經刪除的工作 刪除 刪除工作 沒有任何工作 佇列工作 <strong>不會過期的失敗工作</strong><br/>在沒有任何時間限制下,允許你重新執行工作,您應該重新執行或刪除工作或再次加入佇列。<br />在屬性<code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> 設定可自動刪除 沒有失敗的工作 失敗 失敗的工作 沒有任何工作 擷取工作 歷史圖表走勢 即時圖表走勢 儀表板 建立 確認刪除這個工作嗎? 狀態 <strong>工作被終止</strong> – 在伺服器 <code>{0}</code> 被終止,此工作不在目前 <a href="{1}">執行中伺服器</a> 清單 工作在逾時後自動重試執行,也可以重新執行或手動刪除它。 <strong>工作疑似終止</strong> – 在伺服器 <code>{0}</code>被終止,執行時間已超過一分鐘,工作將會自動重試執行,也可以重新執行或手動刪除它。 背景工作 '{0}' 逾時或無法在伺服器執行此工作 <strong>工作結束</strong>. 工作將被自動移除 <em><abbr data-moment="{0}">{1}</abbr></em>. Job Id 重試 返回 耗時:{0}ms 時間: 下一步 上一步 總筆數 每頁筆數 工作疑似終止 沒有立即執行的工作 執行 執行中的工作 無工作 無佇列工作,嘗試加入工作至佇列 長度 下一個工作 佇列 佇列 取消 無工作 Cron 最後執行 下一個執行 時區 定期工作 執行中… 立即執行 沒有重試的工作 重試 <h4>重試項目正在執行,但此頁面無法顯示</h4> <p> 別擔心,重試項目正在執行中,目前的工作儲存庫無法支援查詢,請試著更新您的工作儲存庫或等待下一個執行時間點。 </p> <p> 請至 <a href="{0}">計劃</a>頁查看所有計劃工作及重試項目 </p> 加入佇列 無工作 佇列 計劃的 計劃工作 沒有執行中的伺服器,工作將不會被執行 心跳 名稱 佇列 執行 工作區 伺服器 無工作 完成 總觸發時間 完成的工作 等待中 刪除 失敗 執行中 計劃 完成 工作 定期工作 重試 伺服器 無法找到目標的函式 佇列 無狀態 佇列 連線數 刪除 失敗 執行中 定期 重試 計劃 伺服器 成功的工作 總連線數 條件 持續 等待中 佇列 佇列 {0} 筆失敗的工作,重試或手動刪除它們 失敗 完成 ================================================ FILE: src/Hangfire.Core/Dashboard/Content/resx/Strings.zh.resx ================================================ text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 别担心,系统正在按照预定指令执行当前的工作任务。但是,当前的作业存储不支持显示此页所需的一些查询。请尝试更新您的存储,或者等到完整命令集实现为止。 作业继续执行中,但是无法显示该页面 没有等待中的作业 选项 父级 等待中的作业 创建 删除 您确定要删除所选的全部作业吗? 删除中... 禁用 删除选中 队列作业 加入队列中... 错误 获取到 编号 作业 作业过期. 作业状态已经发生变化 收起... 更多... N/A 原因 重新加入队列 重试 服务器 状态 未知 没有找到任何已经删除的作业 删除 删除作业 没有任何作业 队列作业 <strong>失败的作业不会过期。</strong> 允许您在没有任何时间压力的情况下重新排队。你应该重新排队或手动删除它们, 或者添加 <code>AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Delete)</code> 属性,系统将会自动删除作业。 没有失败的作业 失败 失败的作业 没有任何作业 任务列队 历史图表走势 实时图表走势 仪表盘 创建 确认删除这个作业吗? 状态 <strong>工作被中止</strong> – 它现在正在由非<a href=\"{1}\">活动状态的服务器</a> <code>{0}</code>处理任务。 系统将在任务重试超时后自动结束,你也可以手动重新执行或将其删除。 <strong>作业疑似终止</strong> – 服务器代码 <code>{0}</code>, 心跳包超过1分钟请求超时后,作业会自动重试。但也可以重新加入队列或者删除作业。 作业 '{0}' 已经过期或不存在于这台服务器。 <strong>任务完成</strong>.系统将在<em><abbr data-moment=\"{0}\">{1}</abbr></em>被自动删除这个任务记录. 任务编号 重试 返回应用 耗时: {0}毫秒 时间: 下一页 上一页 总条数 每页条数 作业疑似终止 没有立即执行的作业 执行 执行中作业 没有作业 队列中不存在。尝试添加作业到队列 长度 下一个作业 队列 队列 取消 没有作业 Cron 表达式无效或者在未来 100 年都不会发生 Cron 最后执行 下一个执行 时区 定期作业 执行中... 立即执行 没有重试的作业 重试 <h4>重试的工作,但是这个页面无法显示</h4><p>别担心,重试执行工作是正常的情况。您当前的作业存储不支持显示此页所需的一些查询。请更新您的存储或完善相关指令集。</p><p>请前往< a href =\"{0}\">计划作业</a>页面查看所有预定的工作及重试任务。</p> 加入队列 没有作业 队列 计划的 计划作业 Active 没有活动服务器。后台作业将不会被执行。 没有在最近一分钟内报告心跳的服务器可能会被终止。如果这些服务器不尽快报告心跳信号,会在超时后被自动移除,这不需要人工操作。 在这些服务器上的未完成作业也会被自动重新排入作业队列,但是你也可以通过 <a href="{0}">执行中作业</a> 页面加速这个过程。 终止运行的服务器会被自动移除 可能已终止 心跳 名称 队列 执行 工作区 服务器 没有作业 完成 消耗时间 完成的作业 等待中 删除 失败 执行中 计划 完成 作业 周期性作业 重试 服务器 找不到目标方法。 队列 无状态 队列 活动连接 已删除任务 失败 执行中 定时 重试 计划 服务器 成功的作业 总连接数 条件 持续 等待中 队列 队列 {0} 没有发现工作任务。请手动重试或删除它们。 失败 完成 ================================================ FILE: src/Hangfire.Core/Dashboard/DashboardContext.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Text.RegularExpressions; using Hangfire.Annotations; using Hangfire.Common; namespace Hangfire.Dashboard { /// /// Provides the context for the Dashboard UI. This class serves as a base class for specific web application frameworks, /// such as ASP.NET Core or OWIN, and is accessible from different Dashboard UI request dispatchers (please see /// ), like pages or other endpoints. /// /// /// The class encapsulates the HTTP request and response details, /// along with settings and services necessary to process dashboard requests. /// It provides an abstraction that allows easy integration with various web frameworks by inheriting from this class /// and implementing the specific behavior required for those frameworks. /// public abstract class DashboardContext { private readonly Lazy _isReadOnlyLazy; /// /// Initializes a new instance of the class. /// /// The job storage used by the Dashboard UI. /// The options for configuring the Dashboard UI. /// /// Thrown when or is null. /// protected DashboardContext([NotNull] JobStorage storage, [NotNull] DashboardOptions options) { Storage = storage ?? throw new ArgumentNullException(nameof(storage)); Options = options ?? throw new ArgumentNullException(nameof(options)); _isReadOnlyLazy = new Lazy(() => Options.IsReadOnlyFunc(this)); } /// /// Gets the instance used by the Dashboard UI. /// public JobStorage Storage { get; } /// /// Gets the for configuring the Dashboard UI. /// public DashboardOptions Options { get; } /// /// Gets or sets the URI match information passed from the configured pathTemplate /// when defining a route in the class. /// public Match UriMatch { get; set; } /// /// Gets the metadata. /// Used by request dispatchers (please see ) to provide request information. /// public DashboardRequest Request { get; protected set; } /// /// Gets the metadata. /// Used by request dispatchers (please see ) to send response information. /// public DashboardResponse Response { get; protected set; } /// /// Gets a value indicating whether the Dashboard UI is in read-only mode to possibly /// hide elements that modify the instance's data. /// public bool IsReadOnly => _isReadOnlyLazy.Value; /// /// Gets or sets the anti-forgery header value. /// public string AntiforgeryHeader { get; set; } /// /// Gets or sets the anti-forgery token value. /// public string AntiforgeryToken { get; set; } /// /// Gets the background job client for the current instance. /// /// An instance of . public virtual IBackgroundJobClient GetBackgroundJobClient() { return new BackgroundJobClient(Storage); } /// /// Gets the recurring job manager for the current instance. /// /// An instance of . public virtual IRecurringJobManager GetRecurringJobManager() { return new RecurringJobManager( Storage, JobFilterProviders.Providers, Options.TimeZoneResolver ?? new DefaultTimeZoneResolver()); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/DashboardMetric.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Dashboard { public class DashboardMetric { public DashboardMetric(string name, Func func) : this(name, name, func) { } public DashboardMetric(string name, string title, Func func) { Name = name; Title = title; Func = func; } public string Name { get; } public Func Func { get; } public string Title { get; set; } public string Url { get; set; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/DashboardMetrics.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Hangfire.Annotations; using Hangfire.Dashboard.Resources; using Hangfire.Storage; namespace Hangfire.Dashboard { public static class DashboardMetrics { private static readonly Dictionary Metrics = new Dictionary(); static DashboardMetrics() { AddMetric(ServerCount); AddMetric(RecurringJobCount); AddMetric(RetriesCount); AddMetric(EnqueuedCountOrNull); AddMetric(FailedCountOrNull); AddMetric(EnqueuedAndQueueCount); AddMetric(ScheduledCount); AddMetric(ProcessingCount); AddMetric(SucceededCount); AddMetric(FailedCount); AddMetric(DeletedCount); AddMetric(AwaitingCount); } public static void AddMetric([NotNull] DashboardMetric metric) { if (metric == null) throw new ArgumentNullException(nameof(metric)); lock (Metrics) { Metrics[metric.Name] = metric; } } public static IEnumerable GetMetrics() { lock (Metrics) { return Metrics.Values.ToList(); } } public static readonly DashboardMetric ServerCount = new DashboardMetric( "servers:count", "Metrics_Servers", static page => new Metric(page.Statistics.Servers) { Style = page.Statistics.Servers == 0 ? MetricStyle.Warning : MetricStyle.Default, Highlighted = page.Statistics.Servers == 0, Title = page.Statistics.Servers == 0 ? "No active servers found. Jobs will not be processed." : null }); public static readonly DashboardMetric RecurringJobCount = new DashboardMetric( "recurring:count", "Metrics_RecurringJobs", static page => new Metric(page.Statistics.Recurring)); public static readonly DashboardMetric RetriesCount = new DashboardMetric( "retries:count", "Metrics_Retries", static page => { long retryCount; if (page.Statistics.Retries.HasValue) { retryCount = page.Statistics.Retries.Value; } else { using (var connection = page.Storage.GetReadOnlyConnection()) { if (!(connection is JobStorageConnection storageConnection)) { return null; } retryCount = storageConnection.GetSetCount("retries"); } } return new Metric(retryCount) { Style = retryCount > 0 ? MetricStyle.Warning : MetricStyle.Default }; }); public static readonly DashboardMetric EnqueuedCountOrNull = new DashboardMetric( "enqueued:count-or-null", "Metrics_EnqueuedCountOrNull", static page => page.Statistics.Enqueued > 0 || page.Statistics.Failed == 0 ? new Metric(page.Statistics.Enqueued > 0 ? page.Statistics.Enqueued : page.Statistics.Scheduled) { Style = page.Statistics.Enqueued + page.Statistics.Scheduled > 0 ? MetricStyle.Info : MetricStyle.Default, Highlighted = page.Statistics.Enqueued > 0 && page.Statistics.Failed == 0 } : null); public static readonly DashboardMetric FailedCountOrNull = new DashboardMetric( "failed:count-or-null", "Metrics_FailedJobs", static page => page.Statistics.Failed > 0 ? new Metric(page.Statistics.Failed) { Style = MetricStyle.Danger, Highlighted = true, Title = string.Format(CultureInfo.CurrentCulture, Strings.Metrics_FailedCountOrNull, page.Statistics.Failed) } : null); public static readonly DashboardMetric EnqueuedAndQueueCount = new DashboardMetric( "enqueued-queues:count", "Metrics_EnqueuedQueuesCount", static page => new Metric($"{page.Statistics.Enqueued:N0} / {page.Statistics.Queues:N0}") { IntValue = page.Statistics.Enqueued, Style = page.Statistics.Enqueued > 0 ? MetricStyle.Info : MetricStyle.Default, Highlighted = page.Statistics.Enqueued > 0 }); public static readonly DashboardMetric ScheduledCount = new DashboardMetric( "scheduled:count", "Metrics_ScheduledJobs", static page => new Metric(page.Statistics.Scheduled) { Style = page.Statistics.Scheduled > 0 ? MetricStyle.Info : MetricStyle.Default }); public static readonly DashboardMetric ProcessingCount = new DashboardMetric( "processing:count", "Metrics_ProcessingJobs", static page => new Metric(page.Statistics.Processing) { Style = page.Statistics.Processing > 0 ? MetricStyle.Warning : MetricStyle.Default }); public static readonly DashboardMetric SucceededCount = new DashboardMetric( "succeeded:count", "Metrics_SucceededJobs", static page => new Metric(page.Statistics.Succeeded)); public static readonly DashboardMetric FailedCount = new DashboardMetric( "failed:count", "Metrics_FailedJobs", static page => new Metric(page.Statistics.Failed) { Style = page.Statistics.Failed > 0 ? MetricStyle.Danger : MetricStyle.Default, Highlighted = page.Statistics.Failed > 0 }); public static readonly DashboardMetric DeletedCount = new DashboardMetric( "deleted:count", "Metrics_DeletedJobs", static page => new Metric(page.Statistics.Deleted)); public static readonly DashboardMetric AwaitingCount = new DashboardMetric( "awaiting:count", "Metrics_AwaitingCount", static page => { long awaitingCount = -1; if (page.Statistics.Awaiting.HasValue) { awaitingCount = page.Statistics.Awaiting.Value; } else { using (var connection = page.Storage.GetReadOnlyConnection()) { if (connection is JobStorageConnection storageConnection) { awaitingCount = storageConnection.GetSetCount("awaiting"); } } } return new Metric(awaitingCount) { Style = awaitingCount > 0 ? MetricStyle.Info : MetricStyle.Default }; }); } } ================================================ FILE: src/Hangfire.Core/Dashboard/DashboardRequest.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using System.Threading.Tasks; namespace Hangfire.Dashboard { /// /// Provides the request details for the Dashboard UI. This class serves as an abstraction for HTTP requests /// and is used within implementations to access request information. /// /// /// The class encapsulates the HTTP request details, providing properties to access /// the method, path, base path, IP addresses, and query or form values. /// This allows for a consistent way to handle requests across different web frameworks. /// public abstract class DashboardRequest { /// /// Gets the HTTP method of the request like "GET" or "POST", that can /// be checked for equality by using the comparer. /// public abstract string Method { get; } /// /// Gets the request path for the current request that doesn't include the , /// like "/jobs/enqueued". /// public abstract string Path { get; } /// /// Gets the base path for the request configured in the request middleware, usually useful /// to reconstruct full URIs like for link generation. /// public abstract string PathBase { get; } /// /// Gets the local IP address from which the request originated. /// public abstract string LocalIpAddress { get; } /// /// Gets the remote IP address from which the request originated. /// public abstract string RemoteIpAddress { get; } /// /// Gets the value of a specific query string parameter. /// /// The key of the query string parameter. /// The value of the query string parameter. public abstract string GetQuery(string key); /// /// Gets the values of a specific form parameter asynchronously, reading the request body if it's a form. /// /// The key of the form parameter. /// A task that represents the asynchronous operation. The task result contains the list of values for the form parameter. public abstract Task> GetFormValuesAsync(string key); } } ================================================ FILE: src/Hangfire.Core/Dashboard/DashboardResponse.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.IO; using System.Threading.Tasks; namespace Hangfire.Dashboard { /// /// Provides the response details for the Dashboard UI. This class serves as an abstraction for HTTP responses /// and is used within implementations to send response information. /// /// /// The class encapsulates the HTTP response details, providing properties and methods /// to set the content type, status code, body, and expiration of the response. /// This allows for a consistent way to handle responses across different web frameworks. /// public abstract class DashboardResponse { /// /// Gets or sets the content type of the response like "application/json". /// /// The content type of the response. public abstract string ContentType { get; set; } /// /// Gets or sets the HTTP status code of the response like 200. /// /// The status code of the response. public abstract int StatusCode { get; set; } /// /// Gets the response body stream, most of the time it's better to use the /// method instead for text data. /// /// The response body stream. public abstract Stream Body { get; } /// /// Sets the expiration time for the response. /// /// The expiration time, or null to remove the expiration header. public abstract void SetExpire(DateTimeOffset? value); /// /// Writes the specified text to the response body asynchronously. /// /// The text to write to the response body. /// A task that represents the asynchronous operation. public abstract Task WriteAsync(string text); } } ================================================ FILE: src/Hangfire.Core/Dashboard/DashboardRoutes.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Reflection; using Hangfire.Annotations; using Hangfire.Dashboard.Pages; using Hangfire.States; namespace Hangfire.Dashboard { /// /// Provides the routing mechanisms for the Dashboard UI. This class is used to register custom /// request dispatchers, allowing developers to write extensions for the Dashboard UI, such as /// custom pages, API endpoints or adding custom JavaScript or CSS files. /// /// /// The class contains a collection of routes that the Dashboard UI uses to dispatch requests to handlers. /// Developers can use this class to add custom scripts, stylesheets, and register custom routes for extending the dashboard functionality. /// /// To add a custom route, use the property which is an instance of . /// /// /// public static class DashboardRoutes { private static readonly List> JavaScripts = new List>(); private static readonly List> Stylesheets = new List>(); private static readonly List> StylesheetsDarkMode = new List>(); internal static volatile int JavaScriptsHashCode; internal static volatile int StylesheetsHashCode; internal static volatile int StylesheetsDarkModeHashCode; static DashboardRoutes() { Routes = new RouteCollection(); Routes.AddRazorPage("/", static _ => new HomePage()); Routes.Add("/stats", new JsonStats()); var executingAssembly = typeof(DashboardRoutes).GetTypeInfo().Assembly; AddStylesheet(executingAssembly, GetContentResourceName("css", "bootstrap.min.css")); AddStylesheet(executingAssembly, GetContentResourceName("css", "Chart.min.css")); AddStylesheet(executingAssembly, GetContentResourceName("css", "hangfire.css")); AddStylesheetDarkMode(executingAssembly, GetContentResourceName("css", "hangfire-dark.css")); AddJavaScript(executingAssembly, GetContentResourceName("js", "jquery-3.7.1.min.js")); AddJavaScript(executingAssembly, GetContentResourceName("js", "bootstrap.min.js")); AddJavaScript(executingAssembly, GetContentResourceName("js", "moment-with-locales.min.js")); AddJavaScript(executingAssembly, GetContentResourceName("js", "Chart.min.js")); AddJavaScript(executingAssembly, GetContentResourceName("js", "chartjs-plugin-streaming.min.js")); AddJavaScript(executingAssembly, GetContentResourceName("js", "hangfire.js")); #region Embedded static content Routes.Add("/js[0-9]+", new CombinedResourceDispatcher("application/javascript", JavaScripts)); Routes.Add("/css[0-9]+", new CombinedResourceDispatcher("text/css", Stylesheets)); Routes.Add("/css-dark[0-9]+", new CombinedResourceDispatcher("text/css", StylesheetsDarkMode)); Routes.Add("/fonts/glyphicons-halflings-regular/eot", new EmbeddedResourceDispatcher( "application/vnd.ms-fontobject", executingAssembly, GetContentResourceName("fonts", "glyphicons-halflings-regular.eot"))); Routes.Add("/fonts/glyphicons-halflings-regular/svg", new EmbeddedResourceDispatcher( "image/svg+xml", executingAssembly, GetContentResourceName("fonts", "glyphicons-halflings-regular.svg"))); Routes.Add("/fonts/glyphicons-halflings-regular/ttf", new EmbeddedResourceDispatcher( "application/octet-stream", executingAssembly, GetContentResourceName("fonts", "glyphicons-halflings-regular.ttf"))); Routes.Add("/fonts/glyphicons-halflings-regular/woff", new EmbeddedResourceDispatcher( "font/woff", executingAssembly, GetContentResourceName("fonts", "glyphicons-halflings-regular.woff"))); Routes.Add("/fonts/glyphicons-halflings-regular/woff2", new EmbeddedResourceDispatcher( "font/woff2", executingAssembly, GetContentResourceName("fonts", "glyphicons-halflings-regular.woff2"))); #endregion #region Razor pages and commands Routes.AddRazorPage("/jobs/enqueued", static _ => new QueuesPage()); Routes.AddRazorPage( "/jobs/enqueued/fetched/(?.+)", static x => new FetchedJobsPage(x.Groups["Queue"].Value)); Routes.AddClientBatchCommand("/jobs/enqueued/delete", static (client, jobId) => client.ChangeState(jobId, CreateDeletedState())); Routes.AddClientBatchCommand("/jobs/enqueued/requeue", static (client, jobId) => client.ChangeState(jobId, CreateEnqueuedState())); Routes.AddRazorPage( "/jobs/enqueued/(?.+)", static x => new EnqueuedJobsPage(x.Groups["Queue"].Value)); Routes.AddRazorPage("/jobs/processing", static _ => new ProcessingJobsPage()); Routes.AddClientBatchCommand( "/jobs/processing/delete", static (client, jobId) => client.ChangeState(jobId, CreateDeletedState(), ProcessingState.StateName)); Routes.AddClientBatchCommand( "/jobs/processing/requeue", static (client, jobId) => client.ChangeState(jobId, CreateEnqueuedState(), ProcessingState.StateName)); Routes.AddRazorPage("/jobs/scheduled", static _ => new ScheduledJobsPage()); Routes.AddClientBatchCommand( "/jobs/scheduled/enqueue", static (client, jobId) => client.ChangeState(jobId, CreateEnqueuedState(), ScheduledState.StateName)); Routes.AddClientBatchCommand( "/jobs/scheduled/delete", static (client, jobId) => client.ChangeState(jobId, CreateDeletedState(), ScheduledState.StateName)); Routes.AddRazorPage("/jobs/succeeded", static _ => new SucceededJobs()); Routes.AddClientBatchCommand( "/jobs/succeeded/requeue", static (client, jobId) => client.ChangeState(jobId, CreateEnqueuedState(), SucceededState.StateName)); Routes.AddRazorPage("/jobs/failed", static _ => new FailedJobsPage()); Routes.AddClientBatchCommand( "/jobs/failed/requeue", static (client, jobId) => client.ChangeState(jobId, CreateEnqueuedState(), FailedState.StateName)); Routes.AddClientBatchCommand( "/jobs/failed/delete", static (client, jobId) => client.ChangeState(jobId, CreateDeletedState(), FailedState.StateName)); Routes.AddRazorPage("/jobs/deleted", static _ => new DeletedJobsPage()); Routes.AddClientBatchCommand( "/jobs/deleted/requeue", static (client, jobId) => client.ChangeState(jobId, CreateEnqueuedState(), DeletedState.StateName)); Routes.AddRazorPage("/jobs/awaiting", static _ => new AwaitingJobsPage()); Routes.AddClientBatchCommand("/jobs/awaiting/enqueue", static (client, jobId) => client.ChangeState( jobId, CreateEnqueuedState(), AwaitingState.StateName)); Routes.AddClientBatchCommand("/jobs/awaiting/delete", static (client, jobId) => client.ChangeState( jobId, CreateDeletedState(), AwaitingState.StateName)); Routes.AddCommand( "/jobs/actions/requeue/(?.+)", static context => { var client = context.GetBackgroundJobClient(); return client.ChangeState(context.UriMatch.Groups["JobId"].Value, CreateEnqueuedState()); }); Routes.AddCommand( "/jobs/actions/delete/(?.+)", static context => { var client = context.GetBackgroundJobClient(); return client.ChangeState(context.UriMatch.Groups["JobId"].Value, CreateDeletedState()); }); Routes.AddRazorPage("/jobs/details/(?.+)", static x => new JobDetailsPage(x.Groups["JobId"].Value)); Routes.AddRazorPage("/recurring", x => new RecurringJobsPage()); Routes.AddRecurringBatchCommand( "/recurring/remove", static (manager, jobId) => manager.RemoveIfExists(jobId)); Routes.AddRecurringBatchCommand( "/recurring/trigger", static (manager, jobId) => manager.Trigger(jobId)); Routes.AddRazorPage("/servers", static _ => new ServersPage()); Routes.AddRazorPage("/retries", static _ => new RetriesPage()); #endregion } /// /// Gets the collection of routes for the Dashboard UI. Use this property to register /// custom request dispatchers. /// public static RouteCollection Routes { get; } /// /// Adds a stylesheet resource embedded into the given assembly to be included in the dashboard. /// /// /// The specified resource should be an embedded resource file within the referenced assembly. /// You can discover embedded resource names by calling the assembly.GetManifestResourceNames() method. /// /// The assembly containing the embedded stylesheet resource. /// The name of the stylesheet embedded resource. /// Thrown if or is null. public static void AddStylesheet([NotNull] Assembly assembly, [NotNull] string resource) { if (assembly == null) throw new ArgumentNullException(nameof(assembly)); if (resource == null) throw new ArgumentNullException(nameof(resource)); lock (Stylesheets) { Stylesheets.Add(Tuple.Create(assembly, resource)); StylesheetsHashCode ^= resource.GetHashCode(); } } /// /// Adds a resource embedded into the given assembly that will only be included in the dashboard /// when the is set to true. /// /// /// The specified resource should be an embedded resource file within the referenced assembly. /// You can discover embedded resource names by calling the assembly.GetManifestResourceNames() method. /// /// The assembly containing the dark-mode stylesheet embedded resource. /// The name of the dark-mode stylesheet embedded resource. /// Thrown if or is null. public static void AddStylesheetDarkMode([NotNull] Assembly assembly, [NotNull] string resource) { if (assembly == null) throw new ArgumentNullException(nameof(assembly)); if (resource == null) throw new ArgumentNullException(nameof(resource)); lock (StylesheetsDarkMode) { StylesheetsDarkMode.Add(Tuple.Create(assembly, resource)); StylesheetsDarkModeHashCode ^= resource.GetHashCode(); } } /// /// Adds a JavaScript resource embedded into the given assembly to be included in the dashboard. /// /// /// The specified resource should be an embedded resource file within the referenced assembly. /// You can discover embedded resource names by calling the assembly.GetManifestResourceNames() method. /// /// The assembly containing the JavaScript embedded resource. /// The name of the JavaScript embedded resource. /// Thrown if or is null. public static void AddJavaScript([NotNull] Assembly assembly, [NotNull] string resource) { if (assembly == null) throw new ArgumentNullException(nameof(assembly)); if (resource == null) throw new ArgumentNullException(nameof(resource)); lock (JavaScripts) { JavaScripts.Add(Tuple.Create(assembly, resource)); JavaScriptsHashCode ^= resource.GetHashCode(); } } internal static string GetContentFolderNamespace(string contentFolder) { return $"{typeof (DashboardRoutes).Namespace}.Content.{contentFolder}"; } internal static string GetContentResourceName(string contentFolder, string resourceName) { return $"{GetContentFolderNamespace(contentFolder)}.{resourceName}"; } private static DeletedState CreateDeletedState() { return new DeletedState { Reason = "Triggered via Dashboard UI" }; } private static EnqueuedState CreateEnqueuedState() { return new EnqueuedState { Reason = "Triggered via Dashboard UI" }; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/EmbeddedResourceDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Reflection; using System.Threading.Tasks; using Hangfire.Annotations; namespace Hangfire.Dashboard { internal class EmbeddedResourceDispatcher : IDashboardDispatcher { private readonly Assembly _assembly; private readonly string _resourceName; private readonly string _contentType; public EmbeddedResourceDispatcher( [NotNull] string contentType, [CanBeNull] Assembly assembly, [CanBeNull] string resourceName) { if (contentType == null) throw new ArgumentNullException(nameof(contentType)); _assembly = assembly; _resourceName = resourceName; _contentType = contentType; } public async Task Dispatch(DashboardContext context) { context.Response.ContentType = _contentType; context.Response.SetExpire(DateTimeOffset.Now.AddYears(1)); await WriteResponse(context.Response).ConfigureAwait(false); } protected virtual Task WriteResponse(DashboardResponse response) { return WriteResource(response, _assembly, _resourceName); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Context can be potentially accessed in derived classes.")] protected async Task WriteResource(DashboardResponse response, Assembly assembly, string resourceName) { using (var inputStream = assembly.GetManifestResourceStream(resourceName)) { if (inputStream == null) { throw new ArgumentException($@"Resource with name {resourceName} not found in assembly {assembly}."); } await inputStream.CopyToAsync(response.Body).ConfigureAwait(false); } } } } ================================================ FILE: src/Hangfire.Core/Dashboard/HtmlHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using Hangfire.Common; using System.ComponentModel; using System.Globalization; using System.Linq.Expressions; using System.Reflection; using System.Text.RegularExpressions; using Hangfire.Annotations; using Hangfire.Dashboard.Pages; using Hangfire.Dashboard.Resources; namespace Hangfire.Dashboard { [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "We use instance methods in this class for better observability.")] public class HtmlHelper { private static readonly Type DisplayNameType; private static readonly Func GetDisplayName; private readonly RazorPage _page; static HtmlHelper() { try { #if !NETSTANDARD1_3 DisplayNameType = typeof(DisplayNameAttribute); #else DisplayNameType = Type.GetType("System.ComponentModel.DisplayNameAttribute, System.ComponentModel.Primitives"); #endif if (DisplayNameType == null) return; var p = Expression.Parameter(typeof(object)); var converted = Expression.Convert(p, DisplayNameType); GetDisplayName = Expression.Lambda>(Expression.Call(converted, "get_DisplayName", null), p).Compile(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // Ignoring } } public HtmlHelper([NotNull] RazorPage page) { if (page == null) throw new ArgumentNullException(nameof(page)); _page = page; } public RazorPage Page => _page; public NonEscapedString Breadcrumbs(string title, [NotNull] IDictionary items) { if (items == null) throw new ArgumentNullException(nameof(items)); return RenderPartial(new Breadcrumbs(title, items)); } public NonEscapedString JobsSidebar() { return RenderPartial(new SidebarMenu(JobsSidebarMenu.Items)); } public NonEscapedString SidebarMenu([NotNull] IEnumerable> items) { if (items == null) throw new ArgumentNullException(nameof(items)); return RenderPartial(new SidebarMenu(items)); } public NonEscapedString BlockMetric([NotNull] DashboardMetric metric) { if (metric == null) throw new ArgumentNullException(nameof(metric)); return RenderPartial(new BlockMetric(metric)); } public NonEscapedString InlineMetric([NotNull] DashboardMetric metric) { if (metric == null) throw new ArgumentNullException(nameof(metric)); return RenderPartial(new InlineMetric(metric)); } public NonEscapedString Paginator([NotNull] Pager pager) { if (pager == null) throw new ArgumentNullException(nameof(pager)); return RenderPartial(new Paginator(pager)); } public NonEscapedString PerPageSelector([NotNull] Pager pager) { if (pager == null) throw new ArgumentNullException(nameof(pager)); return RenderPartial(new PerPageSelector(pager)); } public NonEscapedString RenderPartial(RazorPage partialPage) { partialPage.Assign(_page); return new NonEscapedString(partialPage.ToString()); } public NonEscapedString Raw(string value) { return new NonEscapedString(value); } public NonEscapedString JobId(string jobId, bool shorten = true) { Guid guid; return new NonEscapedString(HtmlEncode(Guid.TryParse(jobId, out guid) ? (shorten ? jobId.Substring(0, 8) + "…" : jobId) : $"#{jobId}")); } public string JobName(Job job) { return JobName(job, includeQueue: true); } public string JobName(Job job, bool includeQueue) { if (job == null) { return Strings.Common_CannotFindTargetMethod; } var jobDisplayNameAttribute = job.Method.GetCustomAttribute(); if (jobDisplayNameAttribute != null) { try { return jobDisplayNameAttribute.Format(_page.Context, job); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { return jobDisplayNameAttribute.DisplayName; } } if (DisplayNameType != null && GetDisplayName != null) { var attribute = job.Method.GetCustomAttribute(DisplayNameType); if (attribute != null) { try { return String.Format( CultureInfo.CurrentCulture, GetDisplayName(attribute), job.Args.ToArray()); } catch (FormatException) { return GetDisplayName(attribute); } } } var displayNameProvider = _page.DashboardOptions.DisplayNameFunc; if (displayNameProvider != null) { try { return displayNameProvider(_page.Context, job); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // Ignoring exceptions } } return job.ToString(includeQueue); } public NonEscapedString StateLabel(string stateName) { return StateLabel(stateName, stateName); } public NonEscapedString StateLabel(string stateName, string text, bool hover = false) { if (String.IsNullOrWhiteSpace(stateName)) { return Raw($"{HtmlEncode(Strings.Common_NoState)}"); } var style = $"background-color: {JobHistoryRenderer.GetForegroundStateColor(stateName)};"; var cssSuffix = JobHistoryRenderer.GetStateCssSuffix(stateName); var cssHover = hover ? "label-hover" : null; if (cssSuffix != null) { return Raw($"{HtmlEncode(text)}"); } return Raw($"{HtmlEncode(text)}"); } public NonEscapedString JobIdLink(string jobId) { return Raw($"{JobId(jobId)}"); } public NonEscapedString JobNameLink(string jobId, Job job) { return JobNameLink(jobId, job, includeQueue: true); } public NonEscapedString JobNameLink(string jobId, Job job, bool includeQueue) { return Raw($"{HtmlEncode(JobName(job, includeQueue))}"); } public NonEscapedString RelativeTime(DateTime value) { return Raw($"{HtmlEncode(value.ToString(CultureInfo.CurrentCulture))}"); } public NonEscapedString MomentTitle(DateTime time, string value) { return Raw($"{HtmlEncode(value)}"); } public NonEscapedString LocalTime(DateTime value) { return Raw($"{HtmlEncode(value.ToString(CultureInfo.CurrentCulture))}"); } public string ToHumanDuration(TimeSpan? duration, bool displaySign = true) { if (duration == null) return null; var builder = new StringBuilder(); if (displaySign) { builder.Append(duration.Value.TotalMilliseconds < 0 ? "-" : "+"); } duration = duration.Value.Duration(); if (duration.Value.Days > 0) { builder.Append($"{duration.Value.Days}d "); } if (duration.Value.Hours > 0) { builder.Append($"{duration.Value.Hours}h "); } if (duration.Value.Minutes > 0) { builder.Append($"{duration.Value.Minutes}m "); } if (duration.Value.TotalHours < 1) { if (duration.Value.Seconds > 0) { builder.Append(duration.Value.Seconds); if (duration.Value.Milliseconds > 0) { builder.Append($".{duration.Value.Milliseconds.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0')}"); } builder.Append("s "); } else { if (duration.Value.Milliseconds > 0) { builder.Append($"{duration.Value.Milliseconds}ms "); } } } if (builder.Length <= 1) { builder.Append(" <1ms "); } builder.Remove(builder.Length - 1, 1); return builder.ToString(); } [Obsolete("This method is unused and will be removed in 2.0.0.")] public string FormatProperties(IDictionary properties) { return String.Join(", ", properties.Select(static x => $"{x.Key}: \"{x.Value}\"")); } public NonEscapedString QueueLabel(string queue) { var label = queue != null ? $"{HtmlEncode(queue)}" : $"{HtmlEncode(Strings.Common_Unknown)}"; return new NonEscapedString(label); } public NonEscapedString ServerId(string serverId) { var parts = serverId.Split(':'); var shortenedId = parts.Length > 1 ? String.Join(":", parts.Take(parts.Length - 1)) : serverId; return new NonEscapedString( $"{HtmlEncode(shortenedId)}"); } private static readonly StackTraceHtmlFragments StackTraceHtmlFragments = new StackTraceHtmlFragments { BeforeFrame = "" , AfterFrame = "", BeforeType = "" , AfterType = "", BeforeMethod = "" , AfterMethod = "", BeforeParameters = "" , AfterParameters = "", BeforeParameterType = "", AfterParameterType = "", BeforeParameterName = "" , AfterParameterName = "", BeforeFile = "" , AfterFile = "", BeforeLine = "" , AfterLine = "", }; public NonEscapedString StackTrace(string stackTrace) { try { return new NonEscapedString(StackTraceFormatter.FormatHtml(stackTrace, StackTraceHtmlFragments)); } catch (RegexMatchTimeoutException) { return new NonEscapedString(HtmlEncode(stackTrace)); } } public string HtmlEncode(string text) { return WebUtility.HtmlEncode(text); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/IDashboardAsyncAuthorizationFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Threading.Tasks; using Hangfire.Annotations; namespace Hangfire.Dashboard { public interface IDashboardAsyncAuthorizationFilter { Task AuthorizeAsync([NotNull] DashboardContext context); } } ================================================ FILE: src/Hangfire.Core/Dashboard/IDashboardAuthorizationFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Annotations; namespace Hangfire.Dashboard { public interface IDashboardAuthorizationFilter { bool Authorize([NotNull] DashboardContext context); } } ================================================ FILE: src/Hangfire.Core/Dashboard/IDashboardDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Threading.Tasks; using Hangfire.Annotations; namespace Hangfire.Dashboard { /// /// Defines the method for dispatching requests within the Dashboard UI. /// Implementations of this interface handle incoming requests to the dashboard and produce appropriate responses. /// /// /// The interface is used to process requests in the Dashboard UI. /// Implement this interface to handle custom routes and manage request processing logic. /// /// To register custom dispatchers, use the class. The DashboardRoutes.Routes /// property allows for adding new routes that the dashboard will use to dispatch requests to custom handlers. /// /// /// public interface IDashboardDispatcher { /// /// Processes the request within the provided . /// /// The context for the current dashboard request, containing information about the request and response. /// A task that represents the asynchronous dispatch operation. Task Dispatch([NotNull] DashboardContext context); } } ================================================ FILE: src/Hangfire.Core/Dashboard/JobDetailsRenderer.cs ================================================ // This file is part of Hangfire. // Copyright © 2020 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Storage.Monitoring; namespace Hangfire.Dashboard { public sealed class JobDetailsRendererDto { public JobDetailsRendererDto([NotNull] RazorPage page, [NotNull] string jobId, [NotNull] JobDetailsDto jobDetails) { Page = page ?? throw new ArgumentNullException(nameof(page)); JobId = jobId ?? throw new ArgumentNullException(nameof(jobId)); JobDetails = jobDetails ?? throw new ArgumentNullException(nameof(jobDetails)); } public RazorPage Page { get; } public string JobId { get; } public JobDetailsDto JobDetails { get; } } internal static class JobDetailsRenderer { private static readonly object SyncRoot = new object(); private static readonly List>> Renderers = new List>>(); public static IEnumerable>> GetRenderers() { lock (SyncRoot) { return Renderers.AsReadOnly(); } } public static void AddRenderer(int order, Func renderer) { lock (SyncRoot) { Renderers.Add(Tuple.Create(order, renderer)); Renderers.Sort(static (x, y) => x.Item1.CompareTo(y.Item1)); } } } } ================================================ FILE: src/Hangfire.Core/Dashboard/JobHistoryRenderer.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Globalization; using System.Text; using Hangfire.Common; using Hangfire.States; namespace Hangfire.Dashboard { public static class JobHistoryRenderer { private static readonly IDictionary, NonEscapedString>> Renderers = new Dictionary, NonEscapedString>>(); private static readonly IDictionary BackgroundStateColors = new Dictionary(); private static readonly IDictionary ForegroundStateColors = new Dictionary(); private static readonly IDictionary StateCssSuffixes = new Dictionary(); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static JobHistoryRenderer() { Register(SucceededState.StateName, SucceededRenderer); Register(FailedState.StateName, FailedRenderer); Register(ProcessingState.StateName, ProcessingRenderer); Register(EnqueuedState.StateName, EnqueuedRenderer); Register(ScheduledState.StateName, ScheduledRenderer); Register(DeletedState.StateName, DeletedRenderer); Register(AwaitingState.StateName, AwaitingRenderer); BackgroundStateColors.Add(EnqueuedState.StateName, "#F5F5F5"); BackgroundStateColors.Add(SucceededState.StateName, "#EDF7ED"); BackgroundStateColors.Add(FailedState.StateName, "#FAEBEA"); BackgroundStateColors.Add(ProcessingState.StateName, "#FCEFDC"); BackgroundStateColors.Add(ScheduledState.StateName, "#E0F3F8"); BackgroundStateColors.Add(DeletedState.StateName, "#ddd"); BackgroundStateColors.Add(AwaitingState.StateName, "#E0F3F8"); ForegroundStateColors.Add(EnqueuedState.StateName, "#999"); ForegroundStateColors.Add(SucceededState.StateName, "#5cb85c"); ForegroundStateColors.Add(FailedState.StateName, "#d9534f"); ForegroundStateColors.Add(ProcessingState.StateName, "#f0ad4e"); ForegroundStateColors.Add(ScheduledState.StateName, "#5bc0de"); ForegroundStateColors.Add(DeletedState.StateName, "#777"); ForegroundStateColors.Add(AwaitingState.StateName, "#5bc0de"); StateCssSuffixes.Add(EnqueuedState.StateName, "active"); StateCssSuffixes.Add(SucceededState.StateName, "success"); StateCssSuffixes.Add(FailedState.StateName, "danger"); StateCssSuffixes.Add(ProcessingState.StateName, "warning"); StateCssSuffixes.Add(ScheduledState.StateName, "info"); StateCssSuffixes.Add(DeletedState.StateName, "inactive"); StateCssSuffixes.Add(AwaitingState.StateName, "info"); } [Obsolete("Use `AddStateCssSuffix` method's logic instead. Will be removed in 2.0.0.")] public static void AddBackgroundStateColor(string stateName, string color) { BackgroundStateColors.Add(stateName, color); } public static string GetBackgroundStateColor(string stateName) { if (stateName == null || !BackgroundStateColors.TryGetValue(stateName, out var color)) { return "inherit"; } return color; } [Obsolete("Use `AddStateCssSuffix` method's logic instead. Will be removed in 2.0.0.")] public static void AddForegroundStateColor(string stateName, string color) { ForegroundStateColors.Add(stateName, color); } public static string GetForegroundStateColor(string stateName) { if (stateName == null || !ForegroundStateColors.TryGetValue(stateName, out var color)) { return "inherit"; } return color; } public static void AddStateCssSuffix(string stateName, string color) { StateCssSuffixes.Add(stateName, color); } public static string GetStateCssSuffix(string stateName) { if (stateName == null || !StateCssSuffixes.TryGetValue(stateName, out var suffix)) { return "inherit"; } return suffix; } public static void Register(string state, Func, NonEscapedString> renderer) { if (!Renderers.ContainsKey(state)) { Renderers.Add(state, renderer); } else { Renderers[state] = renderer; } } public static bool Exists(string state) { return Renderers.ContainsKey(state); } public static NonEscapedString RenderHistory( this HtmlHelper helper, string state, IDictionary properties) { var renderer = Renderers.TryGetValue(state, out var value) ? value : DefaultRenderer; return renderer?.Invoke(helper, properties); } public static NonEscapedString NullRenderer(HtmlHelper helper, IDictionary properties) { return null; } public static NonEscapedString DefaultRenderer(HtmlHelper helper, IDictionary stateData) { if (stateData == null || stateData.Count == 0) return null; var builder = new StringBuilder(); builder.Append("
"); foreach (var item in stateData) { builder.Append($"
{helper.HtmlEncode(item.Key)}
"); builder.Append($"
{helper.HtmlEncode(item.Value)}
"); } builder.Append("
"); return new NonEscapedString(builder.ToString()); } public static NonEscapedString SucceededRenderer(HtmlHelper html, IDictionary stateData) { var builder = new StringBuilder(); builder.Append("
"); var itemsAdded = false; if (stateData.TryGetValue("Latency", out var latencyString)) { var latency = TimeSpan.FromMilliseconds(long.Parse(latencyString, CultureInfo.InvariantCulture)); builder.Append($"
Latency:
{html.HtmlEncode(html.ToHumanDuration(latency, false))}
"); itemsAdded = true; } if (stateData.TryGetValue("PerformanceDuration", out var durationString)) { var duration = TimeSpan.FromMilliseconds(long.Parse(durationString, CultureInfo.InvariantCulture)); builder.Append($"
Duration:
{html.HtmlEncode(html.ToHumanDuration(duration, false))}
"); itemsAdded = true; } if (stateData.TryGetValue("Result", out var resultString) && !String.IsNullOrWhiteSpace(resultString)) { var result = stateData["Result"]; builder.Append($"
Result:
{html.HtmlEncode(result)}
"); itemsAdded = true; } builder.Append("
"); if (!itemsAdded) return null; return new NonEscapedString(builder.ToString()); } private static NonEscapedString FailedRenderer(HtmlHelper html, IDictionary stateData) { var builder = new StringBuilder(); var serverId = stateData.TryGetValue("ServerId", out var value) ? $" ({html.ServerId(value)})" : null; builder.Append( $"

{html.HtmlEncode(stateData["ExceptionType"])}{serverId}

{html.HtmlEncode(stateData["ExceptionMessage"])}

"); if (stateData.TryGetValue("ExceptionDetails", out var details) && !String.IsNullOrWhiteSpace(details)) { var stackTrace = html.StackTrace(details).ToString(); builder.Append($"
{stackTrace}
"); } return new NonEscapedString(builder.ToString()); } private static NonEscapedString ProcessingRenderer(HtmlHelper helper, IDictionary stateData) { var builder = new StringBuilder(); builder.Append("
"); if (!stateData.TryGetValue("ServerId", out var serverId)) { stateData.TryGetValue("ServerName", out serverId); } if (serverId != null) { builder.Append("
Server:
"); builder.Append($"
{helper.ServerId(serverId)}
"); } if (stateData.TryGetValue("WorkerId", out var workerId)) { builder.Append("
Worker:
"); builder.Append($"
{helper.HtmlEncode(workerId.Substring(0, 8))}
"); } else if (stateData.TryGetValue("WorkerNumber", out var workerNumber)) { builder.Append("
Worker:
"); builder.Append($"
#{helper.HtmlEncode(workerNumber)}
"); } builder.Append("
"); return new NonEscapedString(builder.ToString()); } private static NonEscapedString EnqueuedRenderer(HtmlHelper helper, IDictionary stateData) { if (!EnqueuedState.DefaultQueue.Equals(stateData["Queue"], StringComparison.OrdinalIgnoreCase)) { return new NonEscapedString( $"
Queue:
{helper.QueueLabel(stateData["Queue"])}
"); } return null; } private static NonEscapedString ScheduledRenderer(HtmlHelper helper, IDictionary stateData) { var enqueueAt = JobHelper.DeserializeDateTime(stateData["EnqueueAt"]); stateData.TryGetValue("Queue", out var queue); var sb = new StringBuilder(); sb.Append("
"); sb.Append($"
Enqueue at:
{helper.HtmlEncode(enqueueAt.ToString(CultureInfo.CurrentCulture))}
"); if (!String.IsNullOrWhiteSpace(queue)) { sb.Append($"
Queue:
{helper.QueueLabel(queue)}
"); } sb.Append("
"); return new NonEscapedString(sb.ToString()); } private static NonEscapedString AwaitingRenderer(HtmlHelper helper, IDictionary stateData) { var builder = new StringBuilder(); builder.Append("
"); if (stateData.TryGetValue("ParentId", out var parentId)) { builder.Append($"
Parent
{helper.JobIdLink(parentId)}
"); } if (stateData.TryGetValue("NextState", out var nextStateString)) { var nextState = SerializationHelper.Deserialize(nextStateString, SerializationOption.TypedInternal); builder.Append($"
Next State
{helper.StateLabel(nextState?.Name ?? "(no state)")}
"); } if (stateData.TryGetValue("Options", out var optionsDescription)) { if (Enum.TryParse(optionsDescription, out JobContinuationOptions options)) { optionsDescription = options.ToString("G"); } builder.Append($"
Options
{helper.HtmlEncode(optionsDescription)}
"); } builder.Append("
"); return new NonEscapedString(builder.ToString()); } private static NonEscapedString DeletedRenderer(HtmlHelper html, IDictionary stateData) { if (stateData.TryGetValue("Exception", out var exception)) { var exceptionInfo = SerializationHelper.Deserialize(exception); if (exceptionInfo != null) { var commaIndex = exceptionInfo.Type.IndexOf(",", StringComparison.OrdinalIgnoreCase); var typeName = commaIndex > 0 ? exceptionInfo.Type.Substring(0, commaIndex) : exceptionInfo.Type; return new NonEscapedString( $"

{html.HtmlEncode(typeName)}

{html.HtmlEncode(exceptionInfo.Message)}

"); } } return null; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/JobMethodCallRenderer.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using Hangfire.Common; using Hangfire.Dashboard.Resources; using Hangfire.Processing; namespace Hangfire.Dashboard { internal static class JobMethodCallRenderer { // Should not be converted to "readonly" to support https://github.com/HangfireIO/Hangfire/issues/1295 internal static int MaxArgumentToRenderSize = 4096; public static NonEscapedString Render(Job job) { if (job == null) { return new NonEscapedString($"{Encode(Strings.Common_CannotFindTargetMethod)}"); } var builder = new StringBuilder(); builder.Append(WrapKeyword("using")); builder.Append(' '); builder.Append(Encode(job.Type.Namespace)); builder.Append(';'); builder.AppendLine(); builder.AppendLine(); string serviceName = null; if (!job.Method.IsStatic) { serviceName = GetNameWithoutGenericArity(job.Type); if (job.Type.GetTypeInfo().IsInterface && serviceName[0] == 'I' && Char.IsUpper(serviceName[1])) { serviceName = serviceName.Substring(1); } serviceName = Char.ToLower(serviceName[0] #if !NETSTANDARD1_3 , CultureInfo.InvariantCulture #endif ) + serviceName.Substring(1); builder.Append(WrapKeyword("var")); builder.Append( $" {Encode(serviceName)} = Activate<{WrapType(Encode(job.Type.ToGenericTypeString()))}>();"); builder.AppendLine(); } if (job.Method.GetCustomAttribute() != null || job.Method.ReturnType.IsTaskLike(out _)) { builder.Append($"{WrapKeyword("await")} "); } builder.Append(!job.Method.IsStatic ? Encode(serviceName) : WrapType(Encode(job.Type.ToGenericTypeString()))); builder.Append('.'); builder.Append(Encode(job.Method.Name)); if (job.Method.IsGenericMethod) { var genericArgumentTypes = job.Method.GetGenericArguments() .Select(static x => WrapType(x.Name)) .ToArray(); builder.Append($"<{String.Join(", ", genericArgumentTypes)}>"); } builder.Append('('); var parameters = job.Method.GetParameters(); var renderedArguments = new List(parameters.Length); var renderedArgumentsTotalLength = 0; const int splitStringMinLength = 100; for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; #pragma warning disable 618 var arguments = job.Arguments; #pragma warning restore 618 if (i < arguments.Length) { var argument = arguments[i]; if (argument != null && argument.Length > MaxArgumentToRenderSize) { renderedArguments.Add(Encode("")); continue; } string renderedArgument; var enumerableArgument = GetIEnumerableGenericArgument(parameter.ParameterType); object argumentValue; bool isJson = true; try { argumentValue = SerializationHelper.Deserialize(argument, parameter.ParameterType, SerializationOption.User); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // If argument value is not encoded as JSON (an old // way using TypeConverter), we should display it as is. argumentValue = argument; isJson = false; } if (enumerableArgument == null || argumentValue == null) { var argumentRenderer = ArgumentRenderer.GetRenderer(parameter.ParameterType); renderedArgument = argumentRenderer.Render(isJson, argumentValue?.ToString(), argument); } else { var renderedItems = new List(); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var item in argumentValue as IEnumerable) { var argumentRenderer = ArgumentRenderer.GetRenderer(enumerableArgument); renderedItems.Add(argumentRenderer.Render(isJson, item?.ToString(), SerializationHelper.Serialize(item, SerializationOption.User))); } // ReSharper disable once UseStringInterpolation renderedArgument = String.Format( CultureInfo.CurrentCulture, "{0}{1} {{ {2} }}", WrapKeyword("new"), parameter.ParameterType.IsArray ? " []" : "", String.Join(", ", renderedItems)); } renderedArguments.Add(renderedArgument); renderedArgumentsTotalLength += renderedArgument.Length; } else { renderedArguments.Add(Encode("")); } } for (int i = 0; i < renderedArguments.Count; i++) { // TODO: be aware of out of range var parameter = parameters[i]; var tooltipPosition = "top"; var renderedArgument = renderedArguments[i]; if (renderedArgumentsTotalLength > splitStringMinLength) { builder.AppendLine(); builder.Append(" "); tooltipPosition = "left"; } else if (i > 0) { builder.Append(' '); } builder.Append($""); builder.Append(renderedArgument); builder.Append(""); if (i < renderedArguments.Count - 1) { builder.Append(','); } } builder.Append(");"); return new NonEscapedString(builder.ToString()); } private static string WrapIdentifier(string value) { return value; } private static string WrapKeyword(string value) { return Span("keyword", value); } private static string WrapType(string value) { return Span("type", value); } private static string WrapString(string value) { return Span("string", value); } private static string Span(string @class, string value) { return $"{value}"; } private static string Encode(string value) { return WebUtility.HtmlEncode(value); } private static Type GetIEnumerableGenericArgument(Type type) { if (type == typeof(string)) return null; return type.GetTypeInfo().ImplementedInterfaces .Where(static x => x.GetTypeInfo().IsGenericType && x.GetTypeInfo().GetGenericTypeDefinition() == typeof(IEnumerable<>)) .Select(static x => x.GetTypeInfo().GetAllGenericArguments()[0]) .FirstOrDefault(); } public static string GetNameWithoutGenericArity(Type t) { string name = t.Name; int index = name.IndexOf('`'); return index == -1 ? name : name.Substring(0, index); } private sealed class ArgumentRenderer { private string _enclosingString; private Type _deserializationType; private Func _valueRenderer; private ArgumentRenderer() { _enclosingString = "\""; _valueRenderer = static value => value == null ? WrapKeyword("null") : WrapString(value); } public string Render(bool isJson, string deserializedValue, string rawValue) { var builder = new StringBuilder(); if (rawValue == null) { return WrapKeyword("null"); } if (_deserializationType != null) { builder.Append(WrapIdentifier( isJson ? "FromJson" : "Deserialize")); builder.Append("<") .Append(WrapType(Encode(_deserializationType.ToGenericTypeString()))) .Append(WrapIdentifier(">")) .Append('('); builder.Append(WrapString(Encode("\"" + rawValue.Replace("\"", "\\\"") + "\""))); } else { if (deserializedValue != null) { builder.Append(_enclosingString); } builder.Append(_valueRenderer(Encode(deserializedValue))); if (deserializedValue != null) { builder.Append(_enclosingString); } } if (_deserializationType != null) { builder.Append(')'); } return builder.ToString(); } public static ArgumentRenderer GetRenderer(Type type) { if (type.GetTypeInfo().IsEnum) { return new ArgumentRenderer { _enclosingString = String.Empty, _valueRenderer = value => $"{WrapType(type.Name)}.{value}" }; } if (IsNumericType(type)) { return new ArgumentRenderer { _enclosingString = String.Empty, _valueRenderer = WrapIdentifier }; } if (type == typeof(bool)) { return new ArgumentRenderer { _valueRenderer = static value => WrapKeyword(value.ToLowerInvariant()), _enclosingString = String.Empty, }; } if (type == typeof(char)) { return new ArgumentRenderer { _enclosingString = "'", }; } if (type == typeof(string) || type == typeof(object)) { return new ArgumentRenderer { _enclosingString = "\"" }; } if (type == typeof(TimeSpan) || type == typeof(DateTime)) { return new ArgumentRenderer { _enclosingString = String.Empty, _valueRenderer = value => $"{WrapType(type.Name)}.Parse({WrapString($"\"{value}\"")})" }; } if (type == typeof(CancellationToken)) { return new ArgumentRenderer { _enclosingString = String.Empty, _valueRenderer = static _ => $"{WrapType(nameof(CancellationToken))}.None" }; } return new ArgumentRenderer { _deserializationType = type, }; } private static bool IsNumericType(Type type) { if (type == null) return false; switch (type.GetTypeCode()) { case TypeCode.Byte: case TypeCode.Decimal: case TypeCode.Double: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.SByte: case TypeCode.Single: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: return true; case TypeCode.Object: if (IsNullableType(type)) { return IsNumericType(Nullable.GetUnderlyingType(type)); } return false; } return false; } private static bool IsNullableType(Type type) { return type.GetTypeInfo().IsGenericType && type.GetTypeInfo().GetGenericTypeDefinition() == typeof(Nullable<>); } } } internal enum TypeCode { Empty = 0, // Null reference Object = 1, // Instance that isn't a value DBNull = 2, // Database null value Boolean = 3, // Boolean Char = 4, // Unicode character SByte = 5, // Signed 8-bit integer Byte = 6, // Unsigned 8-bit integer Int16 = 7, // Signed 16-bit integer UInt16 = 8, // Unsigned 16-bit integer Int32 = 9, // Signed 32-bit integer UInt32 = 10, // Unsigned 32-bit integer Int64 = 11, // Signed 64-bit integer UInt64 = 12, // Unsigned 64-bit integer Single = 13, // IEEE 32-bit float Double = 14, // IEEE 64-bit double Decimal = 15, // Decimal DateTime = 16, // DateTime String = 18, // Unicode character string } internal static class TypeExtensionMethods { public static TypeCode GetTypeCode(this Type type) { if (type == null) { return TypeCode.Empty; } else if (type == typeof(Boolean)) { return TypeCode.Boolean; } else if (type == typeof(Char)) { return TypeCode.Char; } else if (type == typeof(SByte)) { return TypeCode.SByte; } else if (type == typeof(Byte)) { return TypeCode.Byte; } else if (type == typeof(Int16)) { return TypeCode.Int16; } else if (type == typeof(UInt16)) { return TypeCode.UInt16; } else if (type == typeof(Int32)) { return TypeCode.Int32; } else if (type == typeof(UInt32)) { return TypeCode.UInt32; } else if (type == typeof(Int64)) { return TypeCode.Int64; } else if (type == typeof(UInt64)) { return TypeCode.UInt64; } else if (type == typeof(Single)) { return TypeCode.Single; } else if (type == typeof(Double)) { return TypeCode.Double; } else if (type == typeof(Decimal)) { return TypeCode.Decimal; } else if (type == typeof(DateTime)) { return TypeCode.DateTime; } else if (type == typeof(String)) { return TypeCode.String; } else { return TypeCode.Object; } } } } ================================================ FILE: src/Hangfire.Core/Dashboard/JobsSidebarMenu.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Dashboard.Resources; namespace Hangfire.Dashboard { public static class JobsSidebarMenu { public static readonly List> Items = new List>(); static JobsSidebarMenu() { Items.Add(static page => new MenuItem(Strings.JobsSidebarMenu_Enqueued, page.Url.LinkToQueues()) { Active = page.RequestPath.StartsWith("/jobs/enqueued", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.EnqueuedAndQueueCount }); Items.Add(static page => new MenuItem(Strings.JobsSidebarMenu_Scheduled, page.Url.To("/jobs/scheduled")) { Active = page.RequestPath.StartsWith("/jobs/scheduled", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.ScheduledCount }); Items.Add(static page => new MenuItem(Strings.JobsSidebarMenu_Processing, page.Url.To("/jobs/processing")) { Active = page.RequestPath.StartsWith("/jobs/processing", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.ProcessingCount }); Items.Add(static page => new MenuItem(Strings.JobsSidebarMenu_Succeeded, page.Url.To("/jobs/succeeded")) { Active = page.RequestPath.StartsWith("/jobs/succeeded", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.SucceededCount }); Items.Add(static page => new MenuItem(Strings.JobsSidebarMenu_Failed, page.Url.To("/jobs/failed")) { Active = page.RequestPath.StartsWith("/jobs/failed", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.FailedCount }); Items.Add(static page => new MenuItem(Strings.JobsSidebarMenu_Deleted, page.Url.To("/jobs/deleted")) { Active = page.RequestPath.StartsWith("/jobs/deleted", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.DeletedCount }); Items.Add(static page => new MenuItem(Strings.JobsSidebarMenu_Awaiting, page.Url.To("/jobs/awaiting")) { Active = page.RequestPath.StartsWith("/jobs/awaiting", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.AwaitingCount }); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/JsonStats.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; namespace Hangfire.Dashboard { internal sealed class JsonStats : IDashboardDispatcher { public async Task Dispatch(DashboardContext context) { var requestedMetrics = await context.Request.GetFormValuesAsync("metrics[]").ConfigureAwait(false); var page = new StubPage(); page.Assign(context); var metrics = DashboardMetrics.GetMetrics().Where(x => requestedMetrics.Contains(x.Name)); var result = new Dictionary(); foreach (var metric in metrics) { var value = metric.Func(page); result.Add(metric.Name, value); } var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new JsonConverter[]{ new StringEnumConverter { CamelCaseText = true } } }; var serialized = JsonConvert.SerializeObject(result, settings); context.Response.ContentType = "application/json"; await context.Response.WriteAsync(serialized).ConfigureAwait(false); } private sealed class StubPage : RazorPage { public override void Execute() { } } } } ================================================ FILE: src/Hangfire.Core/Dashboard/LocalRequestsOnlyAuthorizationFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Net; namespace Hangfire.Dashboard { public class LocalRequestsOnlyAuthorizationFilter : IDashboardAuthorizationFilter #if FEATURE_OWIN #pragma warning disable 618 , IAuthorizationFilter #pragma warning restore 618 #endif { public bool Authorize(DashboardContext context) { // if unknown, assume not local if (String.IsNullOrEmpty(context.Request.RemoteIpAddress)) return false; // check if localhost if (context.Request.RemoteIpAddress == "127.0.0.1" || context.Request.RemoteIpAddress == "::1") return true; // compare with local address if (context.Request.RemoteIpAddress == context.Request.LocalIpAddress) return true; // Handle addresses such as ::ffff:127.0.0.1 (IP v4 mapped to IP v6) return IPAddress.TryParse(context.Request.RemoteIpAddress, out IPAddress address) && IPAddress.IsLoopback(address); } #if FEATURE_OWIN public bool Authorize(IDictionary owinEnvironment) { var context = new Microsoft.Owin.OwinContext(owinEnvironment); // if unknown, assume not local if (String.IsNullOrEmpty(context.Request.RemoteIpAddress)) return false; // check if localhost if (context.Request.RemoteIpAddress == "127.0.0.1" || context.Request.RemoteIpAddress == "::1") return true; // compare with local address if (context.Request.RemoteIpAddress == context.Request.LocalIpAddress) return true; // Handle addresses such as ::ffff:127.0.0.1 (IP v4 mapped to IP v6) return IPAddress.TryParse(context.Request.RemoteIpAddress, out IPAddress address) && IPAddress.IsLoopback(address); } #endif } } ================================================ FILE: src/Hangfire.Core/Dashboard/MenuItem.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using System.Linq; namespace Hangfire.Dashboard { public class MenuItem { public MenuItem(string text, string url) { Text = text; Url = url; } public string Text { get; } public string Url { get; } public bool Active { get; set; } public DashboardMetric Metric { get; set; } public DashboardMetric[] Metrics { get; set; } public IEnumerable GetAllMetrics() { var metrics = new List { Metric }; if (Metrics != null) { metrics.AddRange(Metrics); } return metrics.Where(static x => x != null).ToList(); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Metric.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Globalization; namespace Hangfire.Dashboard { public class Metric { public Metric(string value) { Value = value; } public Metric(long value) { Value = value.ToString("N0", CultureInfo.CurrentCulture); IntValue = value; } public string Value { get; } public long IntValue { get; set; } public MetricStyle Style { get; set; } public bool Highlighted { get; set; } public string Title { get; set; } } public enum MetricStyle { Default, Info, Success, Warning, Danger, } internal static class MetricStyleExtensions { public static string ToClassName(this MetricStyle style) { switch (style) { case MetricStyle.Default: return "metric-default"; case MetricStyle.Info: return "metric-info"; case MetricStyle.Success: return "metric-success"; case MetricStyle.Warning: return "metric-warning"; case MetricStyle.Danger: return "metric-danger"; default: return "metric-null"; } } } } ================================================ FILE: src/Hangfire.Core/Dashboard/NavigationMenu.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Dashboard.Resources; namespace Hangfire.Dashboard { public static class NavigationMenu { public static readonly List> Items = new List>(); static NavigationMenu() { Items.Add(static page => new MenuItem(Strings.NavigationMenu_Jobs, page.Url.LinkToQueues()) { Active = page.RequestPath.StartsWith("/jobs", StringComparison.OrdinalIgnoreCase), Metrics = new [] { DashboardMetrics.EnqueuedCountOrNull, DashboardMetrics.FailedCountOrNull } }); Items.Add(static page => new MenuItem(Strings.NavigationMenu_Retries, page.Url.To("/retries")) { Active = page.RequestPath.StartsWith("/retries", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.RetriesCount }); Items.Add(static page => new MenuItem(Strings.NavigationMenu_RecurringJobs, page.Url.To("/recurring")) { Active = page.RequestPath.StartsWith("/recurring", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.RecurringJobCount }); Items.Add(static page => new MenuItem(Strings.NavigationMenu_Servers, page.Url.To("/servers")) { Active = page.RequestPath.Equals("/servers", StringComparison.OrdinalIgnoreCase), Metric = DashboardMetrics.ServerCount }); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/NonEscapedString.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Dashboard { public class NonEscapedString { private readonly string _value; public NonEscapedString(string value) { _value = value; } public override string ToString() { return _value; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Owin/IOwinDashboardAntiforgery.cs ================================================ // This file is part of Hangfire. Copyright © 2018 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; namespace Hangfire.Dashboard.Owin { public interface IOwinDashboardAntiforgery { string HeaderName { get; } string GetToken(IDictionary environment); bool ValidateRequest(IDictionary environment); } } ================================================ FILE: src/Hangfire.Core/Dashboard/Owin/MiddlewareExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Net; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Dashboard.Owin; using Microsoft.Owin; namespace Hangfire.Dashboard { using MidFunc = Func< Func, Task>, Func, Task> >; using BuildFunc = Action< Func< IDictionary, Func< Func, Task>, Func, Task> >>>; [EditorBrowsable(EditorBrowsableState.Never)] public static class MiddlewareExtensions { public static BuildFunc UseHangfireDashboard( [NotNull] this BuildFunc builder, [NotNull] DashboardOptions options, [NotNull] JobStorage storage, [NotNull] RouteCollection routes, [CanBeNull] IOwinDashboardAntiforgery antiforgery) { if (builder == null) throw new ArgumentNullException(nameof(builder)); if (options == null) throw new ArgumentNullException(nameof(options)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (routes == null) throw new ArgumentNullException(nameof(routes)); builder(_ => UseHangfireDashboard(options, storage, routes, antiforgery)); return builder; } public static MidFunc UseHangfireDashboard( [NotNull] DashboardOptions options, [NotNull] JobStorage storage, [NotNull] RouteCollection routes, [CanBeNull] IOwinDashboardAntiforgery antiforgery) { if (options == null) throw new ArgumentNullException(nameof(options)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (routes == null) throw new ArgumentNullException(nameof(routes)); return next => async env => { var owinContext = new OwinContext(env); var context = new OwinDashboardContext(storage, options, env); if (!options.IgnoreAntiforgeryToken && antiforgery != null) { context.AntiforgeryHeader = antiforgery.HeaderName; context.AntiforgeryToken = antiforgery.GetToken(env); } #pragma warning disable 618 if (options.AuthorizationFilters != null) { if (options.AuthorizationFilters.Any(filter => !filter.Authorize(owinContext.Environment))) #pragma warning restore 618 { owinContext.Response.StatusCode = GetUnauthorizedStatusCode(owinContext); return; } } else { // ReSharper disable once LoopCanBeConvertedToQuery foreach (var filter in options.Authorization) { if (!filter.Authorize(context)) { owinContext.Response.StatusCode = GetUnauthorizedStatusCode(owinContext); return; } } foreach (var filter in options.AsyncAuthorization) { if (!await filter.AuthorizeAsync(context)) { owinContext.Response.StatusCode = GetUnauthorizedStatusCode(owinContext); return; } } } if (!options.IgnoreAntiforgeryToken && antiforgery != null && !antiforgery.ValidateRequest(env)) { owinContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; return; } var findResult = routes.FindDispatcher(owinContext.Request.Path.Value); if (findResult == null) { await next(env); return; } context.UriMatch = findResult.Item2; await findResult.Item1.Dispatch(context); }; } private static int GetUnauthorizedStatusCode(IOwinContext owinContext) { return owinContext.Authentication?.User?.Identity?.IsAuthenticated == true ? (int)HttpStatusCode.Forbidden : (int)HttpStatusCode.Unauthorized; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Owin/OwinDashboardContext.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; namespace Hangfire.Dashboard { public sealed class OwinDashboardContext : DashboardContext { public OwinDashboardContext( [NotNull] JobStorage storage, [NotNull] DashboardOptions options, [NotNull] IDictionary environment) : base(storage, options) { if (environment == null) throw new ArgumentNullException(nameof(environment)); Environment = environment; Request = new OwinDashboardRequest(environment); Response = new OwinDashboardResponse(environment); } public IDictionary Environment { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Owin/OwinDashboardContextExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; namespace Hangfire.Dashboard { public static class OwinDashboardContextExtensions { public static IDictionary GetOwinEnvironment([NotNull] this DashboardContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var owinContext = context as OwinDashboardContext; if (owinContext == null) { throw new ArgumentException($"Context argument should be of type `{nameof(OwinDashboardContext)}`!", nameof(context)); } return owinContext.Environment; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Owin/OwinDashboardRequest.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading.Tasks; using Hangfire.Annotations; using Microsoft.Owin; namespace Hangfire.Dashboard { internal sealed class OwinDashboardRequest : DashboardRequest { private const string FormCollectionKey = "Microsoft.Owin.Form#collection"; private readonly IOwinContext _context; public OwinDashboardRequest([NotNull] IDictionary environment) { if (environment == null) throw new ArgumentNullException(nameof(environment)); _context = new OwinContext(environment); } public override string Method => _context.Request.Method; public override string Path => _context.Request.Path.Value; public override string PathBase => _context.Request.PathBase.Value; public override string LocalIpAddress => _context.Request.LocalIpAddress; public override string RemoteIpAddress => _context.Request.RemoteIpAddress; public override string GetQuery(string key) => _context.Request.Query[key]; public override async Task> GetFormValuesAsync(string key) { IList values; if(_context.Environment.TryGetValue(FormCollectionKey, out var formCollection)) { if (formCollection is IFormCollection) { var form = (IFormCollection)_context.Request.Environment[FormCollectionKey]; values = form.GetValues(key); } else { dynamic form = _context.Request.Environment[FormCollectionKey]; values = form.GetValues(key); } } else { var form = await _context.Request.ReadFormAsync().ConfigureAwait(false); values = form.GetValues(key); } return values ?? new List(); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Owin/OwinDashboardResponse.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Hangfire.Annotations; using Microsoft.Owin; namespace Hangfire.Dashboard { internal sealed class OwinDashboardResponse : DashboardResponse { private readonly IOwinContext _context; public OwinDashboardResponse([NotNull] IDictionary environment) { if (environment == null) throw new ArgumentNullException(nameof(environment)); _context = new OwinContext(environment); } public override string ContentType { get { return _context.Response.ContentType; } set { _context.Response.ContentType = value; } } public override int StatusCode { get { return _context.Response.StatusCode; } set { _context.Response.StatusCode = value; } } public override Stream Body => _context.Response.Body; public override void SetExpire(DateTimeOffset? value) { _context.Response.Expires = value; } public override Task WriteAsync(string text) { return _context.Response.WriteAsync(text); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pager.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; namespace Hangfire.Dashboard { public class Pager { private const int PageItemsCount = 7; private const int DefaultRecordsPerPage = 10; private int _startPageIndex = 1; private int _endPageIndex = 1; public Pager(int from, int perPage, long total) : this(from, perPage, DefaultRecordsPerPage, total) { } public Pager(int from, int perPage, int defaultPerPage, long total) { FromRecord = from >= 0 ? from : 0; RecordsPerPage = perPage > 0 ? perPage : defaultPerPage; TotalRecordCount = total; CurrentPage = FromRecord / RecordsPerPage + 1; TotalPageCount = (int)Math.Ceiling((double)TotalRecordCount / RecordsPerPage); PagerItems = GenerateItems(); } public string BasePageUrl { get; set; } public int FromRecord { get; } public int RecordsPerPage { get; } public int CurrentPage { get; } public int TotalPageCount { get; } public long TotalRecordCount { get; } internal ICollection PagerItems { get; } public virtual string PageUrl(int page) { if (page < 1 || page > TotalPageCount) return "#"; return BasePageUrl + "?from=" + (page - 1) * RecordsPerPage + "&count=" + RecordsPerPage; } public string RecordsPerPageUrl(int perPage) { if (perPage <= 0) return "#"; return BasePageUrl + "?from=0&count=" + perPage; } private ICollection GenerateItems() { // start page index _startPageIndex = CurrentPage - PageItemsCount / 2; if (_startPageIndex + PageItemsCount > TotalPageCount) _startPageIndex = TotalPageCount + 1 - PageItemsCount; if (_startPageIndex < 1) _startPageIndex = 1; // end page index _endPageIndex = _startPageIndex + PageItemsCount - 1; if (_endPageIndex > TotalPageCount) _endPageIndex = TotalPageCount; var pagerItems = new List(); if (TotalPageCount == 0) return pagerItems; AddPrevious(pagerItems); // first page if (_startPageIndex > 1) pagerItems.Add(new Item(1, false, ItemType.Page)); // more page before numeric page buttons AddMoreBefore(pagerItems); // numeric page AddPageNumbers(pagerItems); // more page after numeric page buttons AddMoreAfter(pagerItems); // last page if (_endPageIndex < TotalPageCount) pagerItems.Add(new Item(TotalPageCount, false, ItemType.Page)); // Next page AddNext(pagerItems); return pagerItems; } private void AddPrevious(ICollection results) { var item = new Item(CurrentPage - 1, CurrentPage == 1, ItemType.PrevPage); results.Add(item); } private void AddMoreBefore(ICollection results) { if (_startPageIndex > 2) { var index = _startPageIndex - 1; var item = new Item(index, false, ItemType.MorePage); results.Add(item); } } private void AddMoreAfter(ICollection results) { if (_endPageIndex < TotalPageCount - 1) { var index = _startPageIndex + PageItemsCount; if (index > TotalPageCount) { index = TotalPageCount; } var item = new Item(index, false, ItemType.MorePage); results.Add(item); } } private void AddPageNumbers(ICollection results) { for (var pageIndex = _startPageIndex; pageIndex <= _endPageIndex; pageIndex++) { var item = new Item(pageIndex, false, ItemType.Page); results.Add(item); } } private void AddNext(ICollection results) { var item = new Item(CurrentPage + 1, CurrentPage >= TotalPageCount, ItemType.NextPage); results.Add(item); } internal sealed class Item { public Item(int pageIndex, bool disabled, ItemType type) { PageIndex = pageIndex; Disabled = disabled; Type = type; } public int PageIndex { get; } public bool Disabled { get; } public ItemType Type { get; } } internal enum ItemType { Page, PrevPage, NextPage, MorePage } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/AwaitingJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: true *@ @using System @using System.Collections.Generic @using Hangfire @using Hangfire.Common; @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @using Hangfire.States @using Hangfire.Storage @using Hangfire.Storage.Monitoring; @inherits RazorPage @{ Layout = new LayoutPage(Strings.AwaitingJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); List jobIds = null; Pager pager = null; JobList jobs = null; int jobCount = 0; if (Storage.HasFeature(JobStorageFeatures.Monitoring.AwaitingJobs)) { var monitor = Storage.GetMonitoringApi() as JobStorageMonitor; if (monitor == null) { throw new NotSupportedException("MonitoringApi should inherit the `JobStorageMonitor` class"); } pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.AwaitingCount()); jobs = monitor.AwaitingJobs(pager.FromRecord, pager.RecordsPerPage); jobCount = jobs.Count; } else { using (var connection = Storage.GetReadOnlyConnection()) { var storageConnection = connection as JobStorageConnection; if (storageConnection != null) { pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, storageConnection.GetSetCount("awaiting")); jobIds = storageConnection.GetRangeFromSet("awaiting", pager.FromRecord, pager.FromRecord + pager.RecordsPerPage - 1); jobCount = jobIds.Count; } } } }
@Html.JobsSidebar()

@Strings.AwaitingJobsPage_Title

@if (jobIds == null && jobs == null) {

@Strings.AwaitingJobsPage_ContinuationsWarning_Title

@Strings.AwaitingJobsPage_ContinuationsWarning_Text

} else if (jobCount > 0) {
@if (!IsReadOnly) { } @if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @for (var i = 0; i < jobCount; i++) { var jobId = jobIds != null ? jobIds[i] : jobs[i].Key; Job job = null; bool inAwaitingState = true; IDictionary stateData = null; string parentStateName = null; DateTime? awaitingSince = null; if (jobs != null) { if (jobs[i].Value != null) { job = jobs[i].Value.Job; inAwaitingState = jobs[i].Value.InAwaitingState; stateData = jobs[i].Value.StateData; parentStateName = jobs[i].Value.ParentStateName; awaitingSince = jobs[i].Value.AwaitingAt; } } else { using (var connection = Storage.GetReadOnlyConnection()) { var jobData = connection.GetJobData(jobId); var state = connection.GetStateData(jobId); inAwaitingState = AwaitingState.StateName.Equals(state?.Name); if (state != null && inAwaitingState) { var parentState = connection.GetStateData(state.Data["ParentId"]); parentStateName = parentState.Name; } job = jobData.Job; stateData = state?.Data; awaitingSince = jobData.CreatedAt; } } @if (!IsReadOnly) { } @if (job == null) { } else { } }
@Strings.Common_Id @Strings.Common_Job @Strings.AwaitingJobsPage_Table_Options @Strings.AwaitingJobsPage_Table_Parent @Strings.AwaitingJobsPage_Table_Since
@if (job != null && inAwaitingState) { } @Html.JobIdLink(jobId) @if (job != null && !inAwaitingState) { } @Strings.Common_JobExpired @Html.JobNameLink(jobId, job) @if (stateData != null && stateData.ContainsKey("Options") && !String.IsNullOrWhiteSpace(stateData["Options"])) { var optionsDescription = stateData["Options"]; if (Enum.TryParse(optionsDescription, out JobContinuationOptions options)) { optionsDescription = options.ToString("G"); } @optionsDescription } else { @Strings.Common_NotAvailable } @if (parentStateName != null) { @Html.StateLabel(parentStateName, parentStateName, hover: true) } else { @Strings.Common_NotAvailable } @if (awaitingSince.HasValue) { @Html.RelativeTime(awaitingSince.Value) } else { @Strings.Common_NotAvailable }
@Html.Paginator(pager)
} else {
@Strings.AwaitingJobsPage_NoJobs
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/AwaitingJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using System; #line default #line hidden #line 3 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using System.Collections.Generic; #line default #line hidden using System.Linq; using System.Text; #line 4 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire; #line default #line hidden #line 5 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire.Common; #line default #line hidden #line 6 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 7 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 8 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden #line 9 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire.States; #line default #line hidden #line 10 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire.Storage; #line default #line hidden #line 11 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" using Hangfire.Storage.Monitoring; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class AwaitingJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 13 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Layout = new LayoutPage(Strings.AwaitingJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); List jobIds = null; Pager pager = null; JobList jobs = null; int jobCount = 0; if (Storage.HasFeature(JobStorageFeatures.Monitoring.AwaitingJobs)) { var monitor = Storage.GetMonitoringApi() as JobStorageMonitor; if (monitor == null) { throw new NotSupportedException("MonitoringApi should inherit the `JobStorageMonitor` class"); } pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.AwaitingCount()); jobs = monitor.AwaitingJobs(pager.FromRecord, pager.RecordsPerPage); jobCount = jobs.Count; } else { using (var connection = Storage.GetReadOnlyConnection()) { var storageConnection = connection as JobStorageConnection; if (storageConnection != null) { pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, storageConnection.GetSetCount("awaiting")); jobIds = storageConnection.GetRangeFromSet("awaiting", pager.FromRecord, pager.FromRecord + pager.RecordsPerPage - 1); jobCount = jobIds.Count; } } } #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 56 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 59 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.AwaitingJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 61 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" if (jobIds == null && jobs == null) { #line default #line hidden WriteLiteral("
\r\n

"); #line 64 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.AwaitingJobsPage_ContinuationsWarning_Title); #line default #line hidden WriteLiteral("

\r\n

"); #line 65 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.AwaitingJobsPage_ContinuationsWarning_Text); #line default #line hidden WriteLiteral("

\r\n
\r\n"); #line 67 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } else if (jobCount > 0) { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 72 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 80 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } #line default #line hidden #line 81 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 90 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 91 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n " + " \r\n" + " \r\n \r\n"); #line 98 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 103 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 112 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" for (var i = 0; i < jobCount; i++) { var jobId = jobIds != null ? jobIds[i] : jobs[i].Key; Job job = null; bool inAwaitingState = true; IDictionary stateData = null; string parentStateName = null; DateTime? awaitingSince = null; if (jobs != null) { if (jobs[i].Value != null) { job = jobs[i].Value.Job; inAwaitingState = jobs[i].Value.InAwaitingState; stateData = jobs[i].Value.StateData; parentStateName = jobs[i].Value.ParentStateName; awaitingSince = jobs[i].Value.AwaitingAt; } } else { using (var connection = Storage.GetReadOnlyConnection()) { var jobData = connection.GetJobData(jobId); var state = connection.GetStateData(jobId); inAwaitingState = AwaitingState.StateName.Equals(state?.Name); if (state != null && inAwaitingState) { var parentState = connection.GetStateData(state.Data["ParentId"]); parentStateName = parentState.Name; } job = jobData.Job; stateData = state?.Data; awaitingSince = jobData.CreatedAt; } } #line default #line hidden WriteLiteral(" \r\n"); #line 154 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 162 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 170 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" if (job == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 173 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 216 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 218 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 104 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 105 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 106 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.AwaitingJobsPage_Table_Options); #line default #line hidden WriteLiteral(""); #line 107 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.AwaitingJobsPage_Table_Parent); #line default #line hidden WriteLiteral(""); #line 108 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.AwaitingJobsPage_Table_Since); #line default #line hidden WriteLiteral("
\r\n <" + "/div>\r\n "); #line 222 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 224 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n "); #line 228 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" Write(Strings.AwaitingJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 230 "..\..\Dashboard\Pages\AwaitingJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
\r\n"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/DeletedJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Strings.DeletedJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.DeletedListCount()); var jobs = monitor.DeletedJobs(pager.FromRecord, pager.RecordsPerPage); }
@Html.JobsSidebar()

@Strings.DeletedJobsPage_Title

@if (pager.TotalPageCount == 0) {
@Strings.DeletedJobsPage_NoJobs
} else {
@if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @if (jobs.Any(x => x.Value?.StateData != null)) { } @foreach (var job in jobs) { @if (!IsReadOnly) { } @if (job.Value == null) { if (jobs.Any(x => x.Value?.StateData != null)) { } else { } } else { if (job.Value.StateData != null) { ExceptionInfo exception = null; string typeName = null; if (job.Value.StateData.TryGetValue("Exception", out var serializedException) && !String.IsNullOrWhiteSpace(serializedException)) { exception = Common.SerializationHelper.Deserialize(serializedException, Common.SerializationOption.Internal); var commaIndex = exception.Type.IndexOf(",", StringComparison.OrdinalIgnoreCase); typeName = commaIndex > 0 ? exception.Type.Substring(0, commaIndex) : exception.Type; } } } }
@Strings.Common_Id @Strings.Common_Job@Strings.DeletedJobsPage_Table_Exception@Strings.DeletedJobsPage_Table_Deleted
@if (job.Value != null && job.Value.InDeletedState) { } @Html.JobIdLink(job.Key) @if (job.Value != null && !job.Value.InDeletedState) { } @Strings.Common_JobExpired@Strings.Common_JobExpired @Html.JobNameLink(job.Key, job.Value.Job) @if (!String.IsNullOrEmpty(typeName)) { @typeName } @if (job.Value.DeletedAt.HasValue) { @Html.RelativeTime(job.Value.DeletedAt.Value) }
@Html.Paginator(pager)
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/DeletedJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" using Hangfire; #line default #line hidden #line 3 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 4 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 5 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class DeletedJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 7 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Layout = new LayoutPage(Strings.DeletedJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.DeletedListCount()); var jobs = monitor.DeletedJobs(pager.FromRecord, pager.RecordsPerPage); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 22 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 25 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Strings.DeletedJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 27 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" if (pager.TotalPageCount == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 30 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Strings.DeletedJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 32 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 37 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 46 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 47 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n " + " \r\n " + " \r\n \r\n"); #line 53 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 58 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 61 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" if (jobs.Any(x => x.Value?.StateData != null)) { #line default #line hidden WriteLiteral(" \r\n"); #line 64 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n " + " \r\n"); #line 69 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" foreach (var job in jobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 72 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 80 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n\r\n"); #line 89 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" if (job.Value == null) { if (jobs.Any(x => x.Value?.StateData != null)) { #line default #line hidden WriteLiteral(" \r\n"); #line 94 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); #line 98 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } } else { #line default #line hidden WriteLiteral(" \r\n"); #line 105 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" if (job.Value.StateData != null) { ExceptionInfo exception = null; string typeName = null; if (job.Value.StateData.TryGetValue("Exception", out var serializedException) && !String.IsNullOrWhiteSpace(serializedException)) { exception = Common.SerializationHelper.Deserialize(serializedException, Common.SerializationOption.Internal); var commaIndex = exception.Type.IndexOf(",", StringComparison.OrdinalIgnoreCase); typeName = commaIndex > 0 ? exception.Type.Substring(0, commaIndex) : exception.Type; } #line default #line hidden WriteLiteral(" \r\n"); #line 124 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 131 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 133 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 59 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 60 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 63 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Strings.DeletedJobsPage_Table_Exception); #line default #line hidden WriteLiteral(""); #line 65 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Strings.DeletedJobsPage_Table_Deleted); #line default #line hidden WriteLiteral("
\r\n <" + "/div>\r\n\r\n "); #line 138 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 140 "..\..\Dashboard\Pages\DeletedJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
\r\n\r\n"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/EnqueuedJobsPage.cs ================================================ namespace Hangfire.Dashboard.Pages { partial class EnqueuedJobsPage { public EnqueuedJobsPage(string queue) { Queue = queue; } public string Queue { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/EnqueuedJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System.Collections @using System.Collections.Generic @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Queue); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.EnqueuedCount(Queue)); var enqueuedJobs = monitor.EnqueuedJobs(Queue, pager.FromRecord, pager.RecordsPerPage); }
@Html.JobsSidebar()
@Html.Breadcrumbs(Queue, new Dictionary { { "Queues", Url.LinkToQueues() } })

@Queue @Strings.EnqueuedJobsPage_Title

@if (pager.TotalPageCount == 0) {
@Strings.EnqueuedJobsPage_NoJobs
} else {
@if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @foreach (var job in enqueuedJobs) { @if (!IsReadOnly) { } @if (job.Value == null) { } else { } }
@Strings.Common_Id @Strings.Common_State @Strings.Common_Job @Strings.Common_Enqueued
@if (job.Value != null && job.Value.InEnqueuedState) { } @Html.JobIdLink(job.Key) @if (job.Value != null && !job.Value.InEnqueuedState) { } @Strings.Common_JobExpired @Html.StateLabel(job.Value.State) @Html.JobNameLink(job.Key, job.Value.Job) @if (job.Value.EnqueuedAt.HasValue) { @Html.RelativeTime(job.Value.EnqueuedAt.Value) } else { @Strings.Common_NotAvailable }
@Html.Paginator(pager)
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/EnqueuedJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; #line 2 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" using System.Collections; #line default #line hidden #line 3 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" using System.Collections.Generic; #line default #line hidden using System.Linq; using System.Text; #line 4 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" using Hangfire; #line default #line hidden #line 5 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 6 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 7 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class EnqueuedJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 9 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Layout = new LayoutPage(Queue); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.EnqueuedCount(Queue)); var enqueuedJobs = monitor.EnqueuedJobs(Queue, pager.FromRecord, pager.RecordsPerPage); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 24 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 27 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Html.Breadcrumbs(Queue, new Dictionary { { "Queues", Url.LinkToQueues() } })); #line default #line hidden WriteLiteral("\r\n\r\n

"); #line 32 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Queue); #line default #line hidden WriteLiteral(" "); #line 32 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Strings.EnqueuedJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 34 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" if (pager.TotalPageCount == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 37 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Strings.EnqueuedJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 39 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 44 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 54 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 55 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n " + " \r\n " + " \r\n \r\n"); #line 62 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 67 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 75 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" foreach (var job in enqueuedJobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 78 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 86 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 94 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" if (job.Value == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 97 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 116 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 118 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 68 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 69 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Strings.Common_State); #line default #line hidden WriteLiteral(""); #line 70 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 71 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Strings.Common_Enqueued); #line default #line hidden WriteLiteral("
\r\n <" + "/div>\r\n\r\n "); #line 123 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 125 "..\..\Dashboard\Pages\EnqueuedJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/FailedJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Strings.FailedJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.FailedCount()); var failedJobs = monitor.FailedJobs(pager.FromRecord, pager.RecordsPerPage); }
@Html.JobsSidebar()

@Strings.FailedJobsPage_Title

@if (pager.TotalPageCount == 0) {
@Strings.FailedJobsPage_NoJobs
} else {
@Html.Raw(Strings.FailedJobsPage_FailedJobsNotExpire_Warning_Html)
@if (!IsReadOnly) { } @if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @{ var index = 0; } @foreach (var job in failedJobs) { @if (!IsReadOnly) { } @if (job.Value == null) { } else { } if (job.Value != null && job.Value.InFailedState) { } }
@Strings.Common_Id @Strings.Common_Job @Strings.FailedJobsPage_Table_Failed
@if (job.Value != null && job.Value.InFailedState) { } @Html.JobIdLink(job.Key) @if (job.Value != null && !job.Value.InFailedState) { } @Strings.Common_JobExpired
@Html.JobNameLink(job.Key, job.Value.Job)
@if (!String.IsNullOrEmpty(job.Value.ExceptionMessage) || !String.IsNullOrEmpty(job.Value.ExceptionDetails)) { }
@if (job.Value.FailedAt.HasValue) { @Html.RelativeTime(job.Value.FailedAt.Value) }
@{ var displayCss = index++ == 0 ? "display-block" : null; var serverId = job.Value.StateData != null && job.Value.StateData.ContainsKey("ServerId") ? $" ({Html.ServerId(job.Value.StateData["ServerId"])})" : null; }
@Html.Paginator(pager)
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/FailedJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" using System; #line default #line hidden using System.Collections.Generic; using System.Linq; using System.Text; #line 3 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" using Hangfire; #line default #line hidden #line 4 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 5 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 6 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class FailedJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 8 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Layout = new LayoutPage(Strings.FailedJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.FailedCount()); var failedJobs = monitor.FailedJobs(pager.FromRecord, pager.RecordsPerPage); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 23 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 26 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Strings.FailedJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 28 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" if (pager.TotalPageCount == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 31 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Strings.FailedJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 33 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n "); #line 37 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Html.Raw(Strings.FailedJobsPage_FailedJobsNotExpire_Warning_Html)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 39 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 42 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 51 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } #line default #line hidden #line 52 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 61 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 62 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n " + " \r\n " + " \r\n \r\n"); #line 69 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 74 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 81 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" var index = 0; #line default #line hidden #line 82 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" foreach (var job in failedJobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 85 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 93 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 101 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" if (job.Value == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 106 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 126 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 128 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" if (job.Value != null && job.Value.InFailedState) { #line default #line hidden WriteLiteral(" \r\n " + " \r\n \r\n"); #line 149 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 75 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 76 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 77 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Strings.FailedJobsPage_Table_Failed); #line default #line hidden WriteLiteral("
\r\n"); #line 132 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" var displayCss = index++ == 0 ? "display-block" : null; var serverId = job.Value.StateData != null && job.Value.StateData.ContainsKey("ServerId") ? $" ({Html.ServerId(job.Value.StateData["ServerId"])})" : null; #line default #line hidden WriteLiteral(" \r\n " + "
\r\n <" + "/div>\r\n\r\n "); #line 155 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 157 "..\..\Dashboard\Pages\FailedJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/FetchedJobsPage.cs ================================================ namespace Hangfire.Dashboard.Pages { partial class FetchedJobsPage { public FetchedJobsPage(string queue) { Queue = queue; } public string Queue { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/FetchedJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System.Collections @using System.Collections.Generic @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Queue); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.FetchedCount(Queue)); var fetchedJobs = monitor.FetchedJobs(Queue, pager.FromRecord, pager.RecordsPerPage); }
@Html.JobsSidebar()
@Html.Breadcrumbs(Strings.FetchedJobsPage_Title, new Dictionary { { "Queues", Url.LinkToQueues() }, { Queue, Url.Queue(Queue) } })

@Queue @Strings.FetchedJobsPage_Title

@if (pager.TotalPageCount == 0) {
@Strings.FetchedJobsPage_NoJobs
} else {
@if (!IsReadOnly) { } @if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @foreach (var job in fetchedJobs) { @if (!IsReadOnly) { } @if (job.Value == null) { } else { } }
@Strings.Common_Id @Strings.Common_State @Strings.Common_Job @Strings.Common_Fetched
@if (job.Value != null) { } @Html.JobIdLink(job.Key) @Strings.Common_JobExpired @Html.StateLabel(job.Value.State) @Html.JobNameLink(job.Key, job.Value.Job) @if (job.Value.FetchedAt.HasValue) { @Html.RelativeTime(job.Value.FetchedAt.Value) }
@Html.Paginator(pager)
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/FetchedJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; #line 2 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" using System.Collections; #line default #line hidden #line 3 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" using System.Collections.Generic; #line default #line hidden using System.Linq; using System.Text; #line 4 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" using Hangfire; #line default #line hidden #line 5 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 6 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 7 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class FetchedJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 9 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Layout = new LayoutPage(Queue); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.FetchedCount(Queue)); var fetchedJobs = monitor.FetchedJobs(Queue, pager.FromRecord, pager.RecordsPerPage); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 24 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 27 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Html.Breadcrumbs(Strings.FetchedJobsPage_Title, new Dictionary { { "Queues", Url.LinkToQueues() }, { Queue, Url.Queue(Queue) } })); #line default #line hidden WriteLiteral("\r\n\r\n

\r\n "); #line 34 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Queue); #line default #line hidden WriteLiteral(" "); #line 34 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Strings.FetchedJobsPage_Title); #line default #line hidden WriteLiteral("\r\n

\r\n\r\n"); #line 37 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" if (pager.TotalPageCount == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 40 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Strings.FetchedJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 42 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 47 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 56 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } #line default #line hidden #line 57 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 67 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 68 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n " + " \r\n \r\n \r\n"); #line 75 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 80 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 88 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" foreach (var job in fetchedJobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 91 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 99 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 103 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" if (job.Value == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 106 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 121 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 123 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 81 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 82 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Strings.Common_State); #line default #line hidden WriteLiteral(""); #line 83 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 84 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Strings.Common_Fetched); #line default #line hidden WriteLiteral("
\r\n
\r\n\r\n " + " "); #line 128 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 130 "..\..\Dashboard\Pages\FetchedJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/HomePage.cs ================================================ using System.Collections.Generic; namespace Hangfire.Dashboard.Pages { partial class HomePage { public static readonly List Metrics = new List(); } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/HomePage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System @using System.Collections.Generic @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @using Hangfire.Storage @using Newtonsoft.Json @inherits RazorPage @{ Layout = new LayoutPage(Strings.HomePage_Title); IDictionary succeeded = null; IDictionary failed = null; IDictionary deleted = null; var period = Query("period") ?? "day"; var monitor = Storage.GetMonitoringApi(); if ("week".Equals(period, StringComparison.OrdinalIgnoreCase)) { succeeded = monitor.SucceededByDatesCount(); failed = monitor.FailedByDatesCount(); if (Storage.HasFeature(JobStorageFeatures.Monitoring.DeletedStateGraphs) && monitor is JobStorageMonitor jobStorageMonitor) { deleted = jobStorageMonitor.DeletedByDatesCount(); } } else if ("day".Equals(period, StringComparison.OrdinalIgnoreCase)) { succeeded = monitor.HourlySucceededJobs(); failed = monitor.HourlyFailedJobs(); if (Storage.HasFeature(JobStorageFeatures.Monitoring.DeletedStateGraphs) && monitor is JobStorageMonitor jobStorageMonitor) { deleted = jobStorageMonitor.HourlyDeletedJobs(); } } }

@Strings.HomePage_Title

@if (Metrics.Count > 0) {
@foreach (var metric in Metrics) {
@Html.BlockMetric(metric)
}
}

@Strings.HomePage_RealtimeGraph

@Strings.HomePage_HistoryGraph

@if (succeeded != null && failed != null) { }
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/HomePage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\HomePage.cshtml" using System; #line default #line hidden #line 3 "..\..\Dashboard\Pages\HomePage.cshtml" using System.Collections.Generic; #line default #line hidden using System.Linq; using System.Text; #line 4 "..\..\Dashboard\Pages\HomePage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 5 "..\..\Dashboard\Pages\HomePage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 6 "..\..\Dashboard\Pages\HomePage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden #line 7 "..\..\Dashboard\Pages\HomePage.cshtml" using Hangfire.Storage; #line default #line hidden #line 8 "..\..\Dashboard\Pages\HomePage.cshtml" using Newtonsoft.Json; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class HomePage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 10 "..\..\Dashboard\Pages\HomePage.cshtml" Layout = new LayoutPage(Strings.HomePage_Title); IDictionary succeeded = null; IDictionary failed = null; IDictionary deleted = null; var period = Query("period") ?? "day"; var monitor = Storage.GetMonitoringApi(); if ("week".Equals(period, StringComparison.OrdinalIgnoreCase)) { succeeded = monitor.SucceededByDatesCount(); failed = monitor.FailedByDatesCount(); if (Storage.HasFeature(JobStorageFeatures.Monitoring.DeletedStateGraphs) && monitor is JobStorageMonitor jobStorageMonitor) { deleted = jobStorageMonitor.DeletedByDatesCount(); } } else if ("day".Equals(period, StringComparison.OrdinalIgnoreCase)) { succeeded = monitor.HourlySucceededJobs(); failed = monitor.HourlyFailedJobs(); if (Storage.HasFeature(JobStorageFeatures.Monitoring.DeletedStateGraphs) && monitor is JobStorageMonitor jobStorageMonitor) { deleted = jobStorageMonitor.HourlyDeletedJobs(); } } #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 43 "..\..\Dashboard\Pages\HomePage.cshtml" Write(Strings.HomePage_Title); #line default #line hidden WriteLiteral("

\r\n"); #line 44 "..\..\Dashboard\Pages\HomePage.cshtml" if (Metrics.Count > 0) { #line default #line hidden WriteLiteral("
\r\n"); #line 47 "..\..\Dashboard\Pages\HomePage.cshtml" foreach (var metric in Metrics) { #line default #line hidden WriteLiteral("
\r\n "); #line 50 "..\..\Dashboard\Pages\HomePage.cshtml" Write(Html.BlockMetric(metric)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 52 "..\..\Dashboard\Pages\HomePage.cshtml" } #line default #line hidden WriteLiteral("
\r\n"); #line 54 "..\..\Dashboard\Pages\HomePage.cshtml" } #line default #line hidden WriteLiteral("

"); #line 55 "..\..\Dashboard\Pages\HomePage.cshtml" Write(Strings.HomePage_RealtimeGraph); #line default #line hidden WriteLiteral("

\r\n

\r\n "); #line 71 "..\..\Dashboard\Pages\HomePage.cshtml" Write(Strings.HomePage_HistoryGraph); #line default #line hidden WriteLiteral("\r\n

\r\n\r\n"); #line 74 "..\..\Dashboard\Pages\HomePage.cshtml" if (succeeded != null && failed != null) { #line default #line hidden WriteLiteral(" \r\n"); #line 85 "..\..\Dashboard\Pages\HomePage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/JobDetailsPage.cs ================================================ using Hangfire.Annotations; namespace Hangfire.Dashboard.Pages { partial class JobDetailsPage { public JobDetailsPage(string jobId) { JobId = jobId; } public string JobId { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/JobDetailsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System @using System.Collections.Generic @using System.Linq @using Hangfire @using Hangfire.Common @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @using Hangfire.States @using Hangfire.Storage @using Hangfire.Storage.Monitoring @inherits RazorPage @{ var monitor = Storage.GetMonitoringApi(); var job = monitor.JobDetails(JobId); var dto = job != null ? new JobDetailsRendererDto(this, JobId, job) : null; string title = null; if (job != null) { title = job.Job != null ? Html.JobName(job.Job) : null; var historyList = new List(job.History); historyList.Add(new StateHistoryDto { StateName = "Created", CreatedAt = job.CreatedAt ?? default(DateTime) }); job.History = historyList; } title = title ?? Strings.Common_Job; Layout = new LayoutPage(title); }
@Html.JobsSidebar()

@title

@if (job == null) {
@String.Format(Strings.JobDetailsPage_JobExpired, JobId)
} else { var currentState = job.History[0]; if (currentState.StateName == ProcessingState.StateName) { var server = monitor.Servers().FirstOrDefault(x => x.Name == currentState.Data["ServerId"]); if (server == null) {
@Html.Raw(String.Format(Strings.JobDetailsPage_JobAbortedNotActive_Warning_Html, currentState.Data["ServerId"], Url.To("/servers")))
} else if (server.Heartbeat.HasValue && server.Heartbeat < (StorageUtcNow ?? ApplicationUtcNow).Add(DashboardOptions.ServerPossiblyAbortedThreshold.Negate())) {
@Html.Raw(String.Format(Strings.JobDetailsPage_JobAbortedWithHeartbeat_Warning_Html, server.Name))
} } if (job.ExpireAt.HasValue) {
@Html.Raw(String.Format(Strings.JobDetailsPage_JobFinished_Warning_Html, JobHelper.ToTimestamp(job.ExpireAt.Value), job.ExpireAt))
} if (job.Job != null) {
// @Strings.JobDetailsPage_JobId: @Html.JobId(JobId.ToString(), false)
@if (job.Job.Queue != null)
{
// @Strings.QueuesPage_Table_Queue: @job.Job.Queue
}
@JobMethodCallRenderer.Render(job.Job)
} else { var dbgParameters = job.Properties.Where(x => x.Key.StartsWith("DBG_")).ToArray();

@Strings.Common_CannotFindTargetMethod

@foreach (var parameter in dbgParameters) { }
@Strings.JobDetailsPage_JobId
@Html.JobId(JobId, false)
@parameter.Key.Substring(4)
@parameter.Value
} var displayParameters = job.Properties.Where(x => !x.Key.StartsWith("DBG_") && x.Key != "Continuations").ToArray(); if (displayParameters.Length > 0) {

@Strings.JobDetailsPage_Parameters

@foreach (var parameter in displayParameters) { }
@parameter.Key
@parameter.Value
} if (job.Properties.TryGetValue("Continuations", out var serializedContinuations)) { var continuations = ContinuationsSupportAttribute.DeserializeContinuations(serializedContinuations); if (continuations.Count > 0) {

@Strings.Common_Continuations

@foreach (var continuation in continuations) { JobData jobData; using (var connection = Storage.GetReadOnlyConnection()) { jobData = connection.GetJobData(continuation.JobId); } @if (jobData == null) { } else { } }
@Strings.Common_Id @Strings.Common_Condition @Strings.Common_State @Strings.Common_Job @Strings.Common_Created
@String.Format(Strings.JobDetailsPage_JobExpired, continuation.JobId)@Html.JobIdLink(continuation.JobId) @continuation.Options.ToString("G") @Html.StateLabel(jobData.State) @Html.JobNameLink(continuation.JobId, jobData.Job) @Html.RelativeTime(jobData.CreatedAt)
} } if (dto != null) { foreach (var renderer in JobDetailsRenderer.GetRenderers()) { try { @renderer.Item2(dto) } catch (Exception ex) {

@ex.GetType().Name

@ex.Message

@Html.StackTrace(ex.StackTrace)
} } }

@if (job.History.Count > 1) { @if (!IsReadOnly) { } @if (!IsReadOnly) { } } @Strings.JobDetailsPage_State

var index = 0; foreach (var entry in job.History) { var accentColor = JobHistoryRenderer.GetForegroundStateColor(entry.StateName); var backgroundColor = JobHistoryRenderer.GetBackgroundStateColor(entry.StateName); var cardCss = index == 0 ? JobHistoryRenderer.GetStateCssSuffix(entry.StateName) : null; var cardStyle = index == 0 && cardCss == null ? $"border-color: {accentColor}" : null; var cardTitleStyle = index == 0 && cardCss == null ? $"color: {accentColor}" : null; var cardBackgroundStyle = index == 0 && cardCss == null ? $"background-color: {backgroundColor}" : null;

@if (index == job.History.Count - 1) { @Html.RelativeTime(entry.CreatedAt) } else { var duration = Html.ToHumanDuration(entry.CreatedAt - job.History[index + 1].CreatedAt); if (index == 0) { @: @Html.RelativeTime(entry.CreatedAt) (@duration) } else { @: @Html.MomentTitle(entry.CreatedAt, duration) } } @entry.StateName

@if (!String.IsNullOrWhiteSpace(entry.Reason)) {

@entry.Reason

} @{ var rendered = Html.RenderHistory(entry.StateName, entry.Data); } @if (rendered != null) {
@rendered
}
index++; } }
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/JobDetailsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using System; #line default #line hidden #line 3 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using System.Collections.Generic; #line default #line hidden #line 4 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using System.Linq; #line default #line hidden using System.Text; #line 5 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire; #line default #line hidden #line 6 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire.Common; #line default #line hidden #line 7 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 8 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 9 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden #line 10 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire.States; #line default #line hidden #line 11 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire.Storage; #line default #line hidden #line 12 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" using Hangfire.Storage.Monitoring; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class JobDetailsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 14 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" var monitor = Storage.GetMonitoringApi(); var job = monitor.JobDetails(JobId); var dto = job != null ? new JobDetailsRendererDto(this, JobId, job) : null; string title = null; if (job != null) { title = job.Job != null ? Html.JobName(job.Job) : null; var historyList = new List(job.History); historyList.Add(new StateHistoryDto { StateName = "Created", CreatedAt = job.CreatedAt ?? default(DateTime) }); job.History = historyList; } title = title ?? Strings.Common_Job; Layout = new LayoutPage(title); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 37 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 40 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 42 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" if (job == null) { #line default #line hidden WriteLiteral("
\r\n "); #line 45 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(String.Format(Strings.JobDetailsPage_JobExpired, JobId)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 47 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } else { var currentState = job.History[0]; if (currentState.StateName == ProcessingState.StateName) { var server = monitor.Servers().FirstOrDefault(x => x.Name == currentState.Data["ServerId"]); if (server == null) { #line default #line hidden WriteLiteral("
\r\n "); #line 57 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.Raw(String.Format(Strings.JobDetailsPage_JobAbortedNotActive_Warning_Html, currentState.Data["ServerId"], Url.To("/servers")))); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 59 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } else if (server.Heartbeat.HasValue && server.Heartbeat < (StorageUtcNow ?? ApplicationUtcNow).Add(DashboardOptions.ServerPossiblyAbortedThreshold.Negate())) { #line default #line hidden WriteLiteral("
\r\n "); #line 63 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.Raw(String.Format(Strings.JobDetailsPage_JobAbortedWithHeartbeat_Warning_Html, server.Name))); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 65 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } } if (job.ExpireAt.HasValue) { #line default #line hidden WriteLiteral("
\r\n "); #line 71 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.Raw(String.Format(Strings.JobDetailsPage_JobFinished_Warning_Html, JobHelper.ToTimestamp(job.ExpireAt.Value), job.ExpireAt))); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 73 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } if (job.Job != null) { #line default #line hidden WriteLiteral("
\r\n
\r\n
// ");


            
            #line 79 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                                                       Write(Strings.JobDetailsPage_JobId);

            
            #line default
            #line hidden
WriteLiteral(": ");


            
            #line 79 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                                                                                      Write(Html.JobId(JobId.ToString(), false));

            
            #line default
            #line hidden
WriteLiteral("\r\n");


            
            #line 80 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
 if (job.Job.Queue != null)
{

            
            #line default
            #line hidden
WriteLiteral("// ");


            
            #line 82 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                    Write(Strings.QueuesPage_Table_Queue);

            
            #line default
            #line hidden
WriteLiteral(": ");


            
            #line 82 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                                                     Write(job.Job.Queue);

            
            #line default
            #line hidden
WriteLiteral("\r\n");


            
            #line 83 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
}

            
            #line default
            #line hidden

            
            #line 84 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
Write(JobMethodCallRenderer.Render(job.Job));

            
            #line default
            #line hidden
WriteLiteral("\r\n
\r\n
\r\n
\r\n"); #line 88 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } else { var dbgParameters = job.Properties.Where(x => x.Key.StartsWith("DBG_")).ToArray(); #line default #line hidden WriteLiteral("
\r\n

"); #line 94 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.Common_CannotFindTargetMethod); #line default #line hidden WriteLiteral("

\r\n \r\n " + " \r\n \r\n \r\n \r\n"); #line 100 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" foreach (var parameter in dbgParameters) { #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n"); #line 106 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } #line default #line hidden WriteLiteral("
"); #line 97 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.JobDetailsPage_JobId); #line default #line hidden WriteLiteral("
");


            
            #line 98 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                                      Write(Html.JobId(JobId, false));

            
            #line default
            #line hidden
WriteLiteral("
"); #line 103 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(parameter.Key.Substring(4)); #line default #line hidden WriteLiteral("
");


            
            #line 104 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                                          Write(parameter.Value);

            
            #line default
            #line hidden
WriteLiteral("
\r\n
\r\n"); #line 109 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } var displayParameters = job.Properties.Where(x => !x.Key.StartsWith("DBG_") && x.Key != "Continuations").ToArray(); if (displayParameters.Length > 0) { #line default #line hidden WriteLiteral("

"); #line 115 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.JobDetailsPage_Parameters); #line default #line hidden WriteLiteral("

\r\n"); WriteLiteral(" \r\n"); #line 117 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" foreach (var parameter in displayParameters) { #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n"); #line 123 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } #line default #line hidden WriteLiteral("
"); #line 120 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(parameter.Key); #line default #line hidden WriteLiteral("
");


            
            #line 121 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                                      Write(parameter.Value);

            
            #line default
            #line hidden
WriteLiteral("
\r\n"); #line 125 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } if (job.Properties.TryGetValue("Continuations", out var serializedContinuations)) { var continuations = ContinuationsSupportAttribute.DeserializeContinuations(serializedContinuations); if (continuations.Count > 0) { #line default #line hidden WriteLiteral("

"); #line 133 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.Common_Continuations); #line default #line hidden WriteLiteral("

\r\n"); WriteLiteral(@"
\r\n \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 146 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" foreach (var continuation in continuations) { JobData jobData; using (var connection = Storage.GetReadOnlyConnection()) { jobData = connection.GetJobData(continuation.JobId); } #line default #line hidden WriteLiteral(" \r\n"); #line 156 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" if (jobData == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 159 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 167 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 169 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
"); #line 138 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 139 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.Common_Condition); #line default #line hidden WriteLiteral(""); #line 140 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.Common_State); #line default #line hidden WriteLiteral(""); #line 141 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 142 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.Common_Created); #line default #line hidden WriteLiteral("
"); #line 158 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(String.Format(Strings.JobDetailsPage_JobExpired, continuation.JobId)); #line default #line hidden WriteLiteral(""); #line 162 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.JobIdLink(continuation.JobId)); #line default #line hidden WriteLiteral(""); #line 163 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(continuation.Options.ToString("G")); #line default #line hidden WriteLiteral(""); #line 164 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.StateLabel(jobData.State)); #line default #line hidden WriteLiteral(""); #line 165 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.JobNameLink(continuation.JobId, jobData.Job)); #line default #line hidden WriteLiteral(""); #line 166 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Html.RelativeTime(jobData.CreatedAt)); #line default #line hidden WriteLiteral("
\r\n " + "
\r\n"); #line 173 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } } if (dto != null) { foreach (var renderer in JobDetailsRenderer.GetRenderers()) { try { #line default #line hidden #line 182 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(renderer.Item2(dto)); #line default #line hidden #line 182 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } catch (Exception ex) { #line default #line hidden WriteLiteral("

"); #line 186 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(ex.GetType().Name); #line default #line hidden WriteLiteral("

\r\n"); WriteLiteral("

"); #line 187 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(ex.Message); #line default #line hidden WriteLiteral("

\r\n"); WriteLiteral("
");


            
            #line 188 "..\..\Dashboard\Pages\JobDetailsPage.cshtml"
                                            Write(Html.StackTrace(ex.StackTrace));

            
            #line default
            #line hidden
WriteLiteral("
\r\n"); #line 189 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } } } #line default #line hidden WriteLiteral("

\r\n"); #line 194 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" if (job.History.Count > 1) { #line default #line hidden WriteLiteral(" \r\n"); #line 197 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 204 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } #line default #line hidden #line 205 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 213 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 215 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" } #line default #line hidden WriteLiteral("\r\n "); #line 217 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" Write(Strings.JobDetailsPage_State); #line default #line hidden WriteLiteral("\r\n

\r\n"); #line 219 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" var index = 0; foreach (var entry in job.History) { var accentColor = JobHistoryRenderer.GetForegroundStateColor(entry.StateName); var backgroundColor = JobHistoryRenderer.GetBackgroundStateColor(entry.StateName); var cardCss = index == 0 ? JobHistoryRenderer.GetStateCssSuffix(entry.StateName) : null; var cardStyle = index == 0 && cardCss == null ? $"border-color: {accentColor}" : null; var cardTitleStyle = index == 0 && cardCss == null ? $"color: {accentColor}" : null; var cardBackgroundStyle = index == 0 && cardCss == null ? $"background-color: {backgroundColor}" : null; #line default #line hidden WriteLiteral(" \r\n"); #line 272 "..\..\Dashboard\Pages\JobDetailsPage.cshtml" index++; } } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/LayoutPage.cs ================================================ namespace Hangfire.Dashboard.Pages { partial class LayoutPage { public LayoutPage(string title) { Title = title; } public string Title { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/LayoutPage.cshtml ================================================ @* Generator: Template TypeVisibility: Public GeneratePrettyNames: True *@ @using System @using System.Globalization @using System.Reflection @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @Title – @(DashboardOptions.DashboardTitle.Contains("<") ? "Hangfire Dashboard" : DashboardOptions.DashboardTitle) @if (!DashboardOptions.IgnoreAntiforgeryToken) { if (!String.IsNullOrWhiteSpace(Context.AntiforgeryHeader)) { @: } if (!String.IsNullOrWhiteSpace(Context.AntiforgeryToken)) { @: } } @{ var version = GetType().GetTypeInfo().Assembly.GetName().Version; var storageVersion = Storage.GetType().GetTypeInfo().Assembly.GetName().Version; } @if(!string.IsNullOrWhiteSpace(DashboardOptions.FaviconPath)) { @: } else { @: } @if (DashboardOptions.DarkModeEnabled) { @: }
@RenderBody()
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/LayoutPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\LayoutPage.cshtml" using System; #line default #line hidden using System.Collections.Generic; #line 3 "..\..\Dashboard\Pages\LayoutPage.cshtml" using System.Globalization; #line default #line hidden using System.Linq; #line 4 "..\..\Dashboard\Pages\LayoutPage.cshtml" using System.Reflection; #line default #line hidden using System.Text; #line 5 "..\..\Dashboard\Pages\LayoutPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 6 "..\..\Dashboard\Pages\LayoutPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 7 "..\..\Dashboard\Pages\LayoutPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] public partial class LayoutPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); WriteLiteral("\r\n\r\n\r\n "); #line 12 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Title); #line default #line hidden WriteLiteral(" – "); #line 12 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(DashboardOptions.DashboardTitle.Contains("<") ? "Hangfire Dashboard" : DashboardOptions.DashboardTitle); #line default #line hidden WriteLiteral("\r\n \r\n \r\n \r\n \r\n"); #line 17 "..\..\Dashboard\Pages\LayoutPage.cshtml" if (!DashboardOptions.IgnoreAntiforgeryToken) { if (!String.IsNullOrWhiteSpace(Context.AntiforgeryHeader)) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" \r\n"); #line 22 "..\..\Dashboard\Pages\LayoutPage.cshtml" } if (!String.IsNullOrWhiteSpace(Context.AntiforgeryToken)) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" \r\n"); #line 26 "..\..\Dashboard\Pages\LayoutPage.cshtml" } } #line default #line hidden #line 28 "..\..\Dashboard\Pages\LayoutPage.cshtml" var version = GetType().GetTypeInfo().Assembly.GetName().Version; var storageVersion = Storage.GetType().GetTypeInfo().Assembly.GetName().Version; #line default #line hidden WriteLiteral("\r\n"); #line 33 "..\..\Dashboard\Pages\LayoutPage.cshtml" if(!string.IsNullOrWhiteSpace(DashboardOptions.FaviconPath)) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" \r\n"); #line 36 "..\..\Dashboard\Pages\LayoutPage.cshtml" } else { #line default #line hidden WriteLiteral(" "); WriteLiteral(" \r\n"); #line 40 "..\..\Dashboard\Pages\LayoutPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 43 "..\..\Dashboard\Pages\LayoutPage.cshtml" if (DashboardOptions.DarkModeEnabled) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" \r\n"); #line 46 "..\..\Dashboard\Pages\LayoutPage.cshtml" } #line default #line hidden WriteLiteral(@"
\r\n
\r\n "); #line 64 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Html.RenderPartial(new Navigation())); #line default #line hidden WriteLiteral("\r\n"); #line 65 "..\..\Dashboard\Pages\LayoutPage.cshtml" if(@AppPath != null) { #line default #line hidden WriteLiteral("
\r\n \r\n " + "
\r\n \r\n " + " "); #line 81 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Html.RenderPartial(new ErrorAlert())); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n \r\n \r\n "); #line 86 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(RenderBody()); #line default #line hidden WriteLiteral(@"
  • Hangfire "); #line 94 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write($"{version.Major}.{version.Minor}.{version.Build}"); #line default #line hidden WriteLiteral("\r\n \r\n
  • \r\n"); #line 97 "..\..\Dashboard\Pages\LayoutPage.cshtml" if(DashboardOptions.DisplayStorageConnectionString){ #line default #line hidden WriteLiteral("
  • "); #line 98 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Storage); #line default #line hidden WriteLiteral(" "); #line 98 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write($"{storageVersion.Major}.{storageVersion.Minor}.{storageVersion.Build}"); #line default #line hidden WriteLiteral("
  • \r\n"); #line 99 "..\..\Dashboard\Pages\LayoutPage.cshtml" } #line default #line hidden #line 100 "..\..\Dashboard\Pages\LayoutPage.cshtml" if (StorageUtcNow.HasValue) { #line default #line hidden WriteLiteral("
  • "); #line 102 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Strings.LayoutPage_Footer_StorageTime); #line default #line hidden WriteLiteral(" "); #line 102 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Html.LocalTime(StorageUtcNow.Value)); #line default #line hidden WriteLiteral("
  • \r\n"); #line 103 "..\..\Dashboard\Pages\LayoutPage.cshtml" } #line default #line hidden WriteLiteral("
  • \r\n"); #line 105 "..\..\Dashboard\Pages\LayoutPage.cshtml" if (TimeDifference.HasValue && Math.Abs(TimeDifference.Value.TotalSeconds) > 30) { #line default #line hidden WriteLiteral(" \r\n"); #line 110 "..\..\Dashboard\Pages\LayoutPage.cshtml" } else { #line default #line hidden WriteLiteral(" "); #line 113 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Strings.LayoutPage_Footer_Time); #line default #line hidden WriteLiteral(" "); #line 113 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(Html.LocalTime(ApplicationUtcNow)); #line default #line hidden WriteLiteral("\r\n"); #line 114 "..\..\Dashboard\Pages\LayoutPage.cshtml" } #line default #line hidden WriteLiteral("
  • \r\n
  • "); #line 116 "..\..\Dashboard\Pages\LayoutPage.cshtml" Write(String.Format(Strings.LayoutPage_Footer_Generatedms, GenerationTime.Elapsed.TotalMilliseconds.ToString("N"))); #line default #line hidden WriteLiteral("
  • \r\n
\r\n
\r\n
\r\n \r\n " + " \r\n\r\n \r\n \r\n\r\n"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/ProcessingJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System @using System.Linq @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Strings.ProcessingJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.ProcessingCount()); var processingJobs = monitor.ProcessingJobs(pager.FromRecord, pager.RecordsPerPage); var servers = monitor.Servers(); }
@Html.JobsSidebar()

@Strings.ProcessingJobsPage_Title

@if (pager.TotalPageCount == 0) {
@Strings.ProcessingJobsPage_NoJobs
} else {
@if (!IsReadOnly) { } @if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @foreach (var job in processingJobs) { @if (!IsReadOnly) { } @if (job.Value == null) { } else if (!job.Value.InProcessingState) { } else { } }
@Strings.Common_Id @Strings.Common_Server @Strings.Common_Job @Strings.ProcessingJobsPage_Table_Started
@if (job.Value != null && job.Value.InProcessingState) { } @Html.JobIdLink(job.Key) @if (job.Value != null && !job.Value.InProcessingState) { } @Strings.Common_JobExpired@Strings.Common_JobStateChanged_Text @Html.ServerId(job.Value.ServerId) @if (servers.All(x => x.Name != job.Value.ServerId || x.Heartbeat < (StorageUtcNow ?? ApplicationUtcNow).Add(DashboardOptions.ServerPossiblyAbortedThreshold.Negate()))) { } @Html.JobNameLink(job.Key, job.Value.Job) @if (job.Value.StartedAt.HasValue) { @Html.RelativeTime(job.Value.StartedAt.Value) }
@Html.Paginator(pager)
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/ProcessingJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" using System; #line default #line hidden using System.Collections.Generic; #line 3 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" using System.Linq; #line default #line hidden using System.Text; #line 4 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" using Hangfire; #line default #line hidden #line 5 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 6 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 7 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class ProcessingJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 9 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Layout = new LayoutPage(Strings.ProcessingJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.ProcessingCount()); var processingJobs = monitor.ProcessingJobs(pager.FromRecord, pager.RecordsPerPage); var servers = monitor.Servers(); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 25 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 28 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Strings.ProcessingJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 30 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" if (pager.TotalPageCount == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 33 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Strings.ProcessingJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 35 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 40 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 49 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } #line default #line hidden #line 50 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 60 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 61 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n " + " \r\n " + " \r\n \r\n"); #line 68 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 73 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 81 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" foreach (var job in processingJobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 84 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 92 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 100 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" if (job.Value == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 103 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } else if (!job.Value.InProcessingState) { #line default #line hidden WriteLiteral(" \r\n"); #line 107 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 127 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 129 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 74 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 75 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Strings.Common_Server); #line default #line hidden WriteLiteral(""); #line 76 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 77 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Strings.ProcessingJobsPage_Table_Started); #line default #line hidden WriteLiteral("
\r\n <" + "/div>\r\n\r\n "); #line 134 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 136 "..\..\Dashboard\Pages\ProcessingJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/QueuesPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System.Linq @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Strings.QueuesPage_Title); var monitor = Storage.GetMonitoringApi(); var queues = monitor.Queues(); }
@Html.JobsSidebar()

@Strings.QueuesPage_Title

@if (queues.Count == 0) {
@Strings.QueuesPage_NoQueues
} else {
@foreach (var queue in queues) { }
@Strings.QueuesPage_Table_Queue @Strings.QueuesPage_Table_Length @Strings.Common_Fetched @Strings.QueuesPage_Table_NextsJobs
@Html.QueueLabel(queue.Name) @queue.Length @if (queue.Fetched.HasValue) { @queue.Fetched } else { @Strings.Common_NotAvailable } @if (queue.FirstJobs.Count == 0) { @Strings.QueuesPage_NoJobs } else { @foreach (var job in queue.FirstJobs) { @if (job.Value == null) { } else { } }
@Strings.Common_Id @Strings.Common_State @Strings.Common_Job @Strings.Common_Enqueued
@Html.JobIdLink(job.Key) @if (job.Value != null && !job.Value.InEnqueuedState) { } @Strings.Common_JobExpired @Html.StateLabel(job.Value.State) @Html.JobNameLink(job.Key, job.Value.Job) @if (job.Value.EnqueuedAt.HasValue) { @Html.RelativeTime(job.Value.EnqueuedAt.Value) } else { @Strings.Common_NotAvailable }
}
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/QueuesPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; #line 2 "..\..\Dashboard\Pages\QueuesPage.cshtml" using System.Linq; #line default #line hidden using System.Text; #line 3 "..\..\Dashboard\Pages\QueuesPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 4 "..\..\Dashboard\Pages\QueuesPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 5 "..\..\Dashboard\Pages\QueuesPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class QueuesPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 7 "..\..\Dashboard\Pages\QueuesPage.cshtml" Layout = new LayoutPage(Strings.QueuesPage_Title); var monitor = Storage.GetMonitoringApi(); var queues = monitor.Queues(); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 16 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 19 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.QueuesPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 21 "..\..\Dashboard\Pages\QueuesPage.cshtml" if (queues.Count == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 24 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.QueuesPage_NoQueues); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 26 "..\..\Dashboard\Pages\QueuesPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n \r\n \r\n " + " \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 40 "..\..\Dashboard\Pages\QueuesPage.cshtml" foreach (var queue in queues) { #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n"); #line 116 "..\..\Dashboard\Pages\QueuesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
" + ""); #line 33 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.QueuesPage_Table_Queue); #line default #line hidden WriteLiteral(""); #line 34 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.QueuesPage_Table_Length); #line default #line hidden WriteLiteral(""); #line 35 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.Common_Fetched); #line default #line hidden WriteLiteral(""); #line 36 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.QueuesPage_Table_NextsJobs); #line default #line hidden WriteLiteral("
"); #line 43 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Html.QueueLabel(queue.Name)); #line default #line hidden WriteLiteral(""); #line 44 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(queue.Length); #line default #line hidden WriteLiteral("\r\n"); #line 46 "..\..\Dashboard\Pages\QueuesPage.cshtml" if (queue.Fetched.HasValue) { #line default #line hidden WriteLiteral(" \r\n"); #line 51 "..\..\Dashboard\Pages\QueuesPage.cshtml" } else { #line default #line hidden WriteLiteral(" "); #line 54 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.Common_NotAvailable); #line default #line hidden WriteLiteral("\r\n"); #line 55 "..\..\Dashboard\Pages\QueuesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 58 "..\..\Dashboard\Pages\QueuesPage.cshtml" if (queue.FirstJobs.Count == 0) { #line default #line hidden WriteLiteral(" \r\n " + " "); #line 61 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.QueuesPage_NoJobs); #line default #line hidden WriteLiteral("\r\n \r\n"); #line 63 "..\..\Dashboard\Pages\QueuesPage.cshtml" } else { #line default #line hidden WriteLiteral(@" \r\n \r\n \r\n \r\n \r\n " + " \r\n <" + "tbody>\r\n"); #line 76 "..\..\Dashboard\Pages\QueuesPage.cshtml" foreach (var job in queue.FirstJobs) { #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 86 "..\..\Dashboard\Pages\QueuesPage.cshtml" if (job.Value == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 89 "..\..\Dashboard\Pages\QueuesPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 108 "..\..\Dashboard\Pages\QueuesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 110 "..\..\Dashboard\Pages\QueuesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n " + "
"); #line 69 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral("" + ""); #line 70 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.Common_State); #line default #line hidden WriteLiteral(""); #line 71 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 72 "..\..\Dashboard\Pages\QueuesPage.cshtml" Write(Strings.Common_Enqueued); #line default #line hidden WriteLiteral("
\r\n"); #line 113 "..\..\Dashboard\Pages\QueuesPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 120 "..\..\Dashboard\Pages\QueuesPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/RecurringJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: true *@ @using System @using System.Collections.Generic @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @using Hangfire.States @using Hangfire.Storage @inherits RazorPage @{ Layout = new LayoutPage(Strings.RecurringJobsPage_Title); List recurringJobs; int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); Pager pager = null; using (var connection = Storage.GetReadOnlyConnection()) { var storageConnection = connection as JobStorageConnection; if (storageConnection != null) { pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, storageConnection.GetRecurringJobCount()); recurringJobs = storageConnection.GetRecurringJobs(pager.FromRecord, pager.FromRecord + pager.RecordsPerPage - 1); } else { recurringJobs = connection.GetRecurringJobs(); } } }

@Strings.RecurringJobsPage_Title

@if (recurringJobs.Count == 0) {
@Strings.RecurringJobsPage_NoJobs
} else {
@if (!IsReadOnly) { } @if (!IsReadOnly) { } @if (pager != null) { @: @Html.PerPageSelector(pager) }
@if (!IsReadOnly) { } @foreach (var job in recurringJobs) { @if (!IsReadOnly) { } @if (job.Error != null) { } }
@Strings.Common_Id @Strings.RecurringJobsPage_Table_Cron @Strings.RecurringJobsPage_Table_TimeZone @Strings.Common_Job @Strings.RecurringJobsPage_Table_NextExecution @Strings.RecurringJobsPage_Table_LastExecution @Strings.Common_Created
@job.Id @* ReSharper disable once EmptyGeneralCatchClause *@ @{ string cronDescription = null; bool cronError = false; if (!String.IsNullOrEmpty(job.Cron)) { try { RecurringJobEntity.ParseCronExpression(job.Cron); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { cronDescription = ex.Message; cronError = true; } if (cronDescription == null) { #if FEATURE_CRONDESCRIPTOR try { cronDescription = CronExpressionDescriptor.ExpressionDescriptor.GetDescription(job.Cron); } catch (FormatException) { } #endif } } } @if (cronDescription != null) { @if (cronError) { } @job.Cron } else { @job.Cron } @if (!String.IsNullOrWhiteSpace(job.TimeZoneId)) { string displayName; Exception exception = null; try { var resolver = DashboardOptions.TimeZoneResolver ?? new DefaultTimeZoneResolver(); displayName = resolver.GetTimeZoneById(job.TimeZoneId).DisplayName; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { displayName = null; exception = ex; } @job.TimeZoneId @if (exception != null) { } } else { @: UTC } @if (job.Job != null) { @: @Html.JobName(job.Job) } else if (job.LoadException != null && job.LoadException.InnerException != null) { @job.LoadException.InnerException.Message } else if (job.LoadException != null) { @job.LoadException.Message } else { @Strings.Common_NotAvailable } @if (!job.NextExecution.HasValue) { if (job.Error != null) { @Strings.Common_Error } else { @Strings.Common_Disabled } } else if (job.RetryAttempt > 0) { @Html.RelativeTime(job.NextExecution.Value) } else { @Html.RelativeTime(job.NextExecution.Value) } @if (job.LastExecution != null) { if (!String.IsNullOrEmpty(job.LastJobId)) { @{ var cssSuffix = JobHistoryRenderer.GetStateCssSuffix(job.LastJobState ?? EnqueuedState.StateName); } @if (cssSuffix != null) { @Html.RelativeTime(job.LastExecution.Value) } else { @Html.RelativeTime(job.LastExecution.Value) } } else { @Html.RelativeTime(job.LastExecution.Value) } } else { @Strings.Common_NotAvailable } @if (job.CreatedAt != null) { @Html.RelativeTime(job.CreatedAt.Value) } else { N/A }
@Html.StackTrace(job.Error)
@if (pager != null) { @: @Html.Paginator(pager) }
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/RecurringJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using System; #line default #line hidden #line 3 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using System.Collections.Generic; #line default #line hidden using System.Linq; using System.Text; #line 4 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using Hangfire; #line default #line hidden #line 5 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 6 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 7 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden #line 8 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using Hangfire.States; #line default #line hidden #line 9 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" using Hangfire.Storage; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class RecurringJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 11 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Layout = new LayoutPage(Strings.RecurringJobsPage_Title); List recurringJobs; int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); Pager pager = null; using (var connection = Storage.GetReadOnlyConnection()) { var storageConnection = connection as JobStorageConnection; if (storageConnection != null) { pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, storageConnection.GetRecurringJobCount()); recurringJobs = storageConnection.GetRecurringJobs(pager.FromRecord, pager.FromRecord + pager.RecordsPerPage - 1); } else { recurringJobs = connection.GetRecurringJobs(); } } #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 39 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.RecurringJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n"); #line 40 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (recurringJobs.Count == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 43 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.RecurringJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 45 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 50 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 59 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden #line 60 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 70 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden #line 71 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (pager != null) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" "); #line 73 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n"); #line 74 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n\r\n
\r\n " + " \r\n " + " \r\n \r\n"); #line 81 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 86 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 97 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" foreach (var job in recurringJobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 100 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 105 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n"); #line 156 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (!String.IsNullOrWhiteSpace(job.TimeZoneId)) { string displayName; Exception exception = null; try { var resolver = DashboardOptions.TimeZoneResolver ?? new DefaultTimeZoneResolver(); displayName = resolver.GetTimeZoneById(job.TimeZoneId).DisplayName; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { displayName = null; exception = ex; } #line default #line hidden WriteLiteral(" \r\n"); #line 178 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" "); WriteLiteral(" UTC\r\n"); #line 182 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 185 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (job.Job != null) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" "); #line 187 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Html.JobName(job.Job)); #line default #line hidden WriteLiteral("\r\n"); #line 188 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else if (job.LoadException != null && job.LoadException.InnerException != null) { #line default #line hidden WriteLiteral(" "); #line 191 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(job.LoadException.InnerException.Message); #line default #line hidden WriteLiteral("\r\n"); #line 192 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else if (job.LoadException != null) { #line default #line hidden WriteLiteral(" "); #line 195 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(job.LoadException.Message); #line default #line hidden WriteLiteral("\r\n"); #line 196 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" "); #line 199 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.Common_NotAvailable); #line default #line hidden WriteLiteral("\r\n"); #line 200 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 203 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (!job.NextExecution.HasValue) { if (job.Error != null) { #line default #line hidden WriteLiteral(" "); #line 207 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.Common_Error); #line default #line hidden WriteLiteral("\r\n"); #line 208 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); #line 212 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } } else if (job.RetryAttempt > 0) { #line default #line hidden WriteLiteral(" "); #line 217 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Html.RelativeTime(job.NextExecution.Value)); #line default #line hidden WriteLiteral("\r\n"); #line 218 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden #line 221 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Html.RelativeTime(job.NextExecution.Value)); #line default #line hidden #line 221 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 225 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (job.LastExecution != null) { if (!String.IsNullOrEmpty(job.LastJobId)) { #line default #line hidden WriteLiteral(" \r\n"); #line 246 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); #line 252 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } } else { #line default #line hidden WriteLiteral(" "); #line 256 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.Common_NotAvailable); #line default #line hidden WriteLiteral("\r\n"); #line 257 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 260 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (job.CreatedAt != null) { #line default #line hidden #line 262 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Html.RelativeTime(job.CreatedAt.Value)); #line default #line hidden #line 262 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" N/A\r\n"); #line 267 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 269 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (job.Error != null) { #line default #line hidden WriteLiteral(" \r\n " + " \r\n " + " \r\n"); #line 276 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 278 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 87 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 88 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.RecurringJobsPage_Table_Cron); #line default #line hidden WriteLiteral(""); #line 89 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.RecurringJobsPage_Table_TimeZone); #line default #line hidden WriteLiteral(""); #line 90 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 91 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.RecurringJobsPage_Table_NextExecution); #line default #line hidden WriteLiteral(""); #line 92 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.RecurringJobsPage_Table_LastExecution); #line default #line hidden WriteLiteral(""); #line 93 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Strings.Common_Created); #line default #line hidden WriteLiteral("
"); #line 106 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(job.Id); #line default #line hidden WriteLiteral("" + "\r\n "); WriteLiteral("\r\n"); #line 109 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" string cronDescription = null; bool cronError = false; if (!String.IsNullOrEmpty(job.Cron)) { try { RecurringJobEntity.ParseCronExpression(job.Cron); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { cronDescription = ex.Message; cronError = true; } if (cronDescription == null) { #if FEATURE_CRONDESCRIPTOR try { cronDescription = CronExpressionDescriptor.ExpressionDescriptor.GetDescription(job.Cron); } catch (FormatException) { } #endif } } #line default #line hidden WriteLiteral("\r\n"); #line 140 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (cronDescription != null) { #line default #line hidden WriteLiteral(" \r\n"); #line 149 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" "); #line 152 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(job.Cron); #line default #line hidden WriteLiteral("\r\n"); #line 153 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n <" + "/div>\r\n\r\n"); #line 283 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" if (pager != null) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" "); #line 285 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n"); #line 286 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n"); #line 288 "..\..\Dashboard\Pages\RecurringJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/RetriesPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System @using System.Collections.Generic @using Hangfire @using Hangfire.Common @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @using Hangfire.Storage @inherits RazorPage @{ Layout = new LayoutPage(Strings.RetriesPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); Pager pager = null; List jobIds = null; using (var connection = Storage.GetReadOnlyConnection()) { var storageConnection = connection as JobStorageConnection; if (storageConnection != null) { pager = new Pager(@from, perPage, DashboardOptions.DefaultRecordsPerPage, storageConnection.GetSetCount("retries")); jobIds = storageConnection.GetRangeFromSet("retries", pager.FromRecord, pager.FromRecord + pager.RecordsPerPage - 1); } } } @if (pager == null) {
@Html.Raw(String.Format(Strings.RetriesPage_Warning_Html, Url.To("/jobs/scheduled")))
} else {

@Strings.RetriesPage_Title

@if (jobIds.Count == 0) {
@Strings.RetriesPage_NoJobs
} else {
@if (!IsReadOnly) { } @if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @foreach (var jobId in jobIds) { JobData jobData; StateData stateData; using (var connection = Storage.GetReadOnlyConnection()) { jobData = connection.GetJobData(jobId); stateData = connection.GetStateData(jobId); } @if (!IsReadOnly) { } @if (jobData == null) { } else { } }
@Strings.Common_Id @Strings.Common_State @Strings.Common_Job @Strings.Common_Reason @Strings.Common_Retry @Strings.Common_Created
@Html.JobIdLink(jobId) Job expired. @Html.StateLabel(jobData.State) @Html.JobNameLink(jobId, jobData.Job) @(stateData?.Reason) @if (stateData != null && stateData.Data.ContainsKey("EnqueueAt")) { @Html.RelativeTime(JobHelper.DeserializeDateTime(stateData.Data["EnqueueAt"])) } @Html.RelativeTime(jobData.CreatedAt)
@Html.Paginator(pager)
}
} ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/RetriesPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\RetriesPage.cshtml" using System; #line default #line hidden #line 3 "..\..\Dashboard\Pages\RetriesPage.cshtml" using System.Collections.Generic; #line default #line hidden using System.Linq; using System.Text; #line 4 "..\..\Dashboard\Pages\RetriesPage.cshtml" using Hangfire; #line default #line hidden #line 5 "..\..\Dashboard\Pages\RetriesPage.cshtml" using Hangfire.Common; #line default #line hidden #line 6 "..\..\Dashboard\Pages\RetriesPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 7 "..\..\Dashboard\Pages\RetriesPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 8 "..\..\Dashboard\Pages\RetriesPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden #line 9 "..\..\Dashboard\Pages\RetriesPage.cshtml" using Hangfire.Storage; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class RetriesPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 11 "..\..\Dashboard\Pages\RetriesPage.cshtml" Layout = new LayoutPage(Strings.RetriesPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); Pager pager = null; List jobIds = null; using (var connection = Storage.GetReadOnlyConnection()) { var storageConnection = connection as JobStorageConnection; if (storageConnection != null) { pager = new Pager(@from, perPage, DashboardOptions.DefaultRecordsPerPage, storageConnection.GetSetCount("retries")); jobIds = storageConnection.GetRangeFromSet("retries", pager.FromRecord, pager.FromRecord + pager.RecordsPerPage - 1); } } #line default #line hidden WriteLiteral("\r\n"); #line 34 "..\..\Dashboard\Pages\RetriesPage.cshtml" if (pager == null) { #line default #line hidden WriteLiteral("
\r\n "); #line 37 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Html.Raw(String.Format(Strings.RetriesPage_Warning_Html, Url.To("/jobs/scheduled")))); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 39 "..\..\Dashboard\Pages\RetriesPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n

"); #line 44 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.RetriesPage_Title); #line default #line hidden WriteLiteral("

\r\n"); #line 45 "..\..\Dashboard\Pages\RetriesPage.cshtml" if (jobIds.Count == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 48 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.RetriesPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 50 "..\..\Dashboard\Pages\RetriesPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 55 "..\..\Dashboard\Pages\RetriesPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 64 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden #line 65 "..\..\Dashboard\Pages\RetriesPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 75 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 76 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n \r\n \r\n " + " \r\n"); #line 83 "..\..\Dashboard\Pages\RetriesPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 88 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n"); #line 98 "..\..\Dashboard\Pages\RetriesPage.cshtml" foreach (var jobId in jobIds) { JobData jobData; StateData stateData; using (var connection = Storage.GetReadOnlyConnection()) { jobData = connection.GetJobData(jobId); stateData = connection.GetStateData(jobId); } #line default #line hidden WriteLiteral(" \r\n"); #line 110 "..\..\Dashboard\Pages\RetriesPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 115 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 119 "..\..\Dashboard\Pages\RetriesPage.cshtml" if (jobData == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 122 "..\..\Dashboard\Pages\RetriesPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 143 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 145 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n "); #line 89 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 90 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.Common_State); #line default #line hidden WriteLiteral(""); #line 91 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 92 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.Common_Reason); #line default #line hidden WriteLiteral(""); #line 93 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.Common_Retry); #line default #line hidden WriteLiteral(""); #line 94 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Strings.Common_Created); #line default #line hidden WriteLiteral("
\r\n " + "
\r\n\r\n "); #line 150 "..\..\Dashboard\Pages\RetriesPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 152 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 155 "..\..\Dashboard\Pages\RetriesPage.cshtml" } #line default #line hidden } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/ScheduledJobsPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Strings.ScheduledJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.ScheduledCount()); var scheduledJobs = monitor.ScheduledJobs(pager.FromRecord, pager.RecordsPerPage); }
@Html.JobsSidebar()

@Strings.ScheduledJobsPage_Title

@if (pager.TotalPageCount == 0) {
@Strings.ScheduledJobsPage_NoJobs
} else {
@if (!IsReadOnly) { } @if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @foreach (var job in scheduledJobs) { @if (!IsReadOnly) { } @if (job.Value == null) { } else { } }
@Strings.Common_Id @Strings.ScheduledJobsPage_Table_Enqueue @Strings.Common_Job @Strings.ScheduledJobsPage_Table_Scheduled
@if (job.Value != null && job.Value.InScheduledState) { } @Html.JobIdLink(job.Key) @if (job.Value != null && !job.Value.InScheduledState) { } @Strings.Common_JobExpired @Html.RelativeTime(job.Value.EnqueueAt) @Html.JobNameLink(job.Key, job.Value.Job) @if (job.Value.ScheduledAt.HasValue) { @Html.RelativeTime(job.Value.ScheduledAt.Value) }
@Html.Paginator(pager)
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/ScheduledJobsPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" using Hangfire; #line default #line hidden #line 3 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 4 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 5 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class ScheduledJobsPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 7 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Layout = new LayoutPage(Strings.ScheduledJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.ScheduledCount()); var scheduledJobs = monitor.ScheduledJobs(pager.FromRecord, pager.RecordsPerPage); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 22 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 25 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Strings.ScheduledJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 27 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" if (pager.TotalPageCount == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 30 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Strings.ScheduledJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 32 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 37 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 46 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } #line default #line hidden #line 47 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 57 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 58 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n " + " \r\n " + " \r\n \r\n"); #line 65 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 70 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n"); #line 77 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" foreach (var job in scheduledJobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 80 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 88 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 96 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" if (job.Value == null) { #line default #line hidden WriteLiteral(" \r\n"); #line 99 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 114 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 116 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n " + " \r\n " + " "); #line 71 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 72 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Strings.ScheduledJobsPage_Table_Enqueue); #line default #line hidden WriteLiteral(""); #line 73 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 74 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Strings.ScheduledJobsPage_Table_Scheduled); #line default #line hidden WriteLiteral("
\r\n
\r\n\r\n "); #line 120 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 122 "..\..\Dashboard\Pages\ScheduledJobsPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/ServersPage.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System @using System.Linq @using Hangfire.Common @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Strings.ServersPage_Title); var monitor = Storage.GetMonitoringApi(); var servers = monitor.Servers(); var now = StorageUtcNow ?? ApplicationUtcNow; var inconclusiveThreshold = DashboardOptions.ServerPossiblyAbortedThreshold; var possiblyAbortedThreshold = TimeSpan.FromSeconds(inconclusiveThreshold.TotalSeconds * 2); }

@Strings.ServersPage_Title

@if (servers.Count == 0) {
@Strings.ServersPage_NoServers
} else { if (servers.Any(x => x.Heartbeat.HasValue && x.Heartbeat.Value < now.Add(-possiblyAbortedThreshold))) {

@Strings.ServersPage_Note_Title

@Html.Raw(string.Format(Strings.ServersPage_Note_Text, Url.To("/jobs/processing")))
}
@foreach (var server in servers) { }
@Strings.ServersPage_Table_Name @Strings.ServersPage_Table_Workers @Strings.ServersPage_Table_Queues @Strings.ServersPage_Table_Started @Strings.ServersPage_Table_Heartbeat
@if (server.Heartbeat < now.Add(-possiblyAbortedThreshold)) { @: @Html.ServerId(server.Name) } else if (server.Heartbeat < now.Add(-inconclusiveThreshold)) { @: @Html.ServerId(server.Name) } else { @: @Html.ServerId(server.Name) } @server.WorkersCount @Html.Raw(String.Join(", ", server.Queues.Select(Html.QueueLabel))) @Html.RelativeTime(server.StartedAt) @if (server.Heartbeat.HasValue) { @Html.RelativeTime(server.Heartbeat.Value) }
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/ServersPage.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\ServersPage.cshtml" using System; #line default #line hidden using System.Collections.Generic; #line 3 "..\..\Dashboard\Pages\ServersPage.cshtml" using System.Linq; #line default #line hidden using System.Text; #line 4 "..\..\Dashboard\Pages\ServersPage.cshtml" using Hangfire.Common; #line default #line hidden #line 5 "..\..\Dashboard\Pages\ServersPage.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 6 "..\..\Dashboard\Pages\ServersPage.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 7 "..\..\Dashboard\Pages\ServersPage.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class ServersPage : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 9 "..\..\Dashboard\Pages\ServersPage.cshtml" Layout = new LayoutPage(Strings.ServersPage_Title); var monitor = Storage.GetMonitoringApi(); var servers = monitor.Servers(); var now = StorageUtcNow ?? ApplicationUtcNow; var inconclusiveThreshold = DashboardOptions.ServerPossiblyAbortedThreshold; var possiblyAbortedThreshold = TimeSpan.FromSeconds(inconclusiveThreshold.TotalSeconds * 2); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 21 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 23 "..\..\Dashboard\Pages\ServersPage.cshtml" if (servers.Count == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 26 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_NoServers); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 28 "..\..\Dashboard\Pages\ServersPage.cshtml" } else { if (servers.Any(x => x.Heartbeat.HasValue && x.Heartbeat.Value < now.Add(-possiblyAbortedThreshold))) { #line default #line hidden WriteLiteral("
\r\n

"); #line 34 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_Note_Title); #line default #line hidden WriteLiteral("

\r\n "); #line 35 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Html.Raw(string.Format(Strings.ServersPage_Note_Text, Url.To("/jobs/processing")))); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 37 "..\..\Dashboard\Pages\ServersPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n \r\n \r\n " + " \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + " \r\n"); #line 51 "..\..\Dashboard\Pages\ServersPage.cshtml" foreach (var server in servers) { #line default #line hidden WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n \r\n"); #line 78 "..\..\Dashboard\Pages\ServersPage.cshtml" } #line default #line hidden WriteLiteral(" \r\n
"); #line 43 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_Table_Name); #line default #line hidden WriteLiteral(""); #line 44 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_Table_Workers); #line default #line hidden WriteLiteral(""); #line 45 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_Table_Queues); #line default #line hidden WriteLiteral(""); #line 46 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_Table_Started); #line default #line hidden WriteLiteral(""); #line 47 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Strings.ServersPage_Table_Heartbeat); #line default #line hidden WriteLiteral("
\r\n"); #line 55 "..\..\Dashboard\Pages\ServersPage.cshtml" if (server.Heartbeat < now.Add(-possiblyAbortedThreshold)) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" "); #line 57 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Html.ServerId(server.Name)); #line default #line hidden WriteLiteral("\r\n"); #line 58 "..\..\Dashboard\Pages\ServersPage.cshtml" } else if (server.Heartbeat < now.Add(-inconclusiveThreshold)) { #line default #line hidden WriteLiteral(" "); WriteLiteral(" "); #line 61 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Html.ServerId(server.Name)); #line default #line hidden WriteLiteral("\r\n"); #line 62 "..\..\Dashboard\Pages\ServersPage.cshtml" } else { #line default #line hidden WriteLiteral(" "); WriteLiteral(" "); #line 65 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Html.ServerId(server.Name)); #line default #line hidden WriteLiteral("\r\n"); #line 66 "..\..\Dashboard\Pages\ServersPage.cshtml" } #line default #line hidden WriteLiteral(" "); #line 68 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(server.WorkersCount); #line default #line hidden WriteLiteral(""); #line 69 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Html.Raw(String.Join(", ", server.Queues.Select(Html.QueueLabel)))); #line default #line hidden WriteLiteral(""); #line 70 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Html.RelativeTime(server.StartedAt)); #line default #line hidden WriteLiteral("\r\n"); #line 72 "..\..\Dashboard\Pages\ServersPage.cshtml" if (server.Heartbeat.HasValue) { #line default #line hidden #line 74 "..\..\Dashboard\Pages\ServersPage.cshtml" Write(Html.RelativeTime(server.Heartbeat.Value)); #line default #line hidden #line 74 "..\..\Dashboard\Pages\ServersPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 82 "..\..\Dashboard\Pages\ServersPage.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/SucceededJobs.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True *@ @using System @using Hangfire @using Hangfire.Dashboard @using Hangfire.Dashboard.Pages @using Hangfire.Dashboard.Resources @inherits RazorPage @{ Layout = new LayoutPage(Strings.SucceededJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.SucceededListCount()); var succeededJobs = monitor.SucceededJobs(pager.FromRecord, pager.RecordsPerPage); }
@Html.JobsSidebar()

@Strings.SucceededJobsPage_Title

@if (pager.TotalPageCount == 0) {
@Strings.SucceededJobsPage_NoJobs
} else {
@if (!IsReadOnly) { } @Html.PerPageSelector(pager)
@if (!IsReadOnly) { } @if (succeededJobs.Any(x => x.Value?.StateData != null)) { } else { } @foreach (var job in succeededJobs) { @if (!IsReadOnly) { } @if (job.Value == null) { if (succeededJobs.Any(x => x.Value?.StateData != null)) { } else { } } else { if (job.Value.StateData != null) { } else { } } }
@Strings.Common_Id @Strings.Common_Job@Strings.SucceededJobsPage_Table_Duration @Strings.SucceededJobsPage_Table_Latency@Strings.SucceededJobsPage_Table_TotalDuration@Strings.SucceededJobsPage_Table_Succeeded
@if (job.Value != null && job.Value.InSucceededState) { } @Html.JobIdLink(job.Key) @if (job.Value != null && !job.Value.InSucceededState) { } @Strings.Common_JobExpired@Strings.Common_JobExpired @Html.JobNameLink(job.Key, job.Value.Job) @if (job.Value.StateData.TryGetValue("PerformanceDuration", out var duration)) { @Html.ToHumanDuration(TimeSpan.FromMilliseconds(long.Parse(duration, System.Globalization.CultureInfo.InvariantCulture)), false) } @if (job.Value.StateData.TryGetValue("Latency", out var latency)) { @Html.ToHumanDuration(TimeSpan.FromMilliseconds(long.Parse(latency, System.Globalization.CultureInfo.InvariantCulture)), false) } @if (job.Value.TotalDuration.HasValue) { @Html.ToHumanDuration(TimeSpan.FromMilliseconds(job.Value.TotalDuration.Value), false) } @if (job.Value.SucceededAt.HasValue) { @Html.RelativeTime(job.Value.SucceededAt.Value) }
@Html.Paginator(pager)
}
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/SucceededJobs.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { #line 2 "..\..\Dashboard\Pages\SucceededJobs.cshtml" using System; #line default #line hidden using System.Collections.Generic; using System.Linq; using System.Text; #line 3 "..\..\Dashboard\Pages\SucceededJobs.cshtml" using Hangfire; #line default #line hidden #line 4 "..\..\Dashboard\Pages\SucceededJobs.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 5 "..\..\Dashboard\Pages\SucceededJobs.cshtml" using Hangfire.Dashboard.Pages; #line default #line hidden #line 6 "..\..\Dashboard\Pages\SucceededJobs.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class SucceededJobs : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 8 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Layout = new LayoutPage(Strings.SucceededJobsPage_Title); int from, perPage; int.TryParse(Query("from"), out from); int.TryParse(Query("count"), out perPage); var monitor = Storage.GetMonitoringApi(); var pager = new Pager(from, perPage, DashboardOptions.DefaultRecordsPerPage, monitor.SucceededListCount()); var succeededJobs = monitor.SucceededJobs(pager.FromRecord, pager.RecordsPerPage); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n "); #line 23 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Html.JobsSidebar()); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n

"); #line 26 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.SucceededJobsPage_Title); #line default #line hidden WriteLiteral("

\r\n\r\n"); #line 28 "..\..\Dashboard\Pages\SucceededJobs.cshtml" if (pager.TotalPageCount == 0) { #line default #line hidden WriteLiteral("
\r\n "); #line 31 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.SucceededJobsPage_NoJobs); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 33 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } else { #line default #line hidden WriteLiteral("
\r\n
\r\n"); #line 38 "..\..\Dashboard\Pages\SucceededJobs.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 47 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral(" "); #line 48 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Html.PerPageSelector(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n\r\n
\r\n " + " \r\n " + " \r\n \r\n"); #line 55 "..\..\Dashboard\Pages\SucceededJobs.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 60 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n"); #line 63 "..\..\Dashboard\Pages\SucceededJobs.cshtml" if (succeededJobs.Any(x => x.Value?.StateData != null)) { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 67 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); #line 71 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral(" \r\n \r\n \r\n " + " \r\n"); #line 76 "..\..\Dashboard\Pages\SucceededJobs.cshtml" foreach (var job in succeededJobs) { #line default #line hidden WriteLiteral(" \r\n"); #line 79 "..\..\Dashboard\Pages\SucceededJobs.cshtml" if (!IsReadOnly) { #line default #line hidden WriteLiteral(" \r\n"); #line 87 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral(" \r\n\r\n"); #line 96 "..\..\Dashboard\Pages\SucceededJobs.cshtml" if (job.Value == null) { if (succeededJobs.Any(x => x.Value?.StateData != null)) { #line default #line hidden WriteLiteral(" \r\n"); #line 101 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); #line 105 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } } else { #line default #line hidden WriteLiteral(" \r\n"); #line 112 "..\..\Dashboard\Pages\SucceededJobs.cshtml" if (job.Value.StateData != null) { #line default #line hidden WriteLiteral(" \r\n"); WriteLiteral(" \r\n"); #line 126 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } else { #line default #line hidden WriteLiteral(" \r\n"); #line 135 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 142 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral(" \r\n"); #line 144 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral(" \r\n
\r\n " + " \r\n " + " "); #line 61 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.Common_Id); #line default #line hidden WriteLiteral(""); #line 62 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.Common_Job); #line default #line hidden WriteLiteral(""); #line 65 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.SucceededJobsPage_Table_Duration); #line default #line hidden WriteLiteral(""); #line 66 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.SucceededJobsPage_Table_Latency); #line default #line hidden WriteLiteral(""); #line 70 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.SucceededJobsPage_Table_TotalDuration); #line default #line hidden WriteLiteral(""); #line 72 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Strings.SucceededJobsPage_Table_Succeeded); #line default #line hidden WriteLiteral("
\r\n <" + "/div>\r\n\r\n "); #line 149 "..\..\Dashboard\Pages\SucceededJobs.cshtml" Write(Html.Paginator(pager)); #line default #line hidden WriteLiteral("\r\n
\r\n"); #line 151 "..\..\Dashboard\Pages\SucceededJobs.cshtml" } #line default #line hidden WriteLiteral("
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_BlockMetric.cs ================================================ namespace Hangfire.Dashboard.Pages { partial class BlockMetric { public BlockMetric(DashboardMetric dashboardMetric) { DashboardMetric = dashboardMetric; } public DashboardMetric DashboardMetric { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_BlockMetric.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard @using Hangfire.Dashboard.Resources @inherits RazorPage @{ var metric = DashboardMetric.Func(this); var className = metric == null ? "metric-null" : metric.Style.ToClassName(); var highlighted = metric != null && metric.Highlighted ? "highlighted" : null; } @if (!string.IsNullOrEmpty(DashboardMetric.Url)) { @: }
@(metric?.Value)
@(Strings.ResourceManager.GetString(DashboardMetric.Title) ?? DashboardMetric.Title)
@if (!string.IsNullOrEmpty(DashboardMetric.Url)) { @:
} ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_BlockMetric.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_BlockMetric.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 3 "..\..\Dashboard\Pages\_BlockMetric.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class BlockMetric : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 5 "..\..\Dashboard\Pages\_BlockMetric.cshtml" var metric = DashboardMetric.Func(this); var className = metric == null ? "metric-null" : metric.Style.ToClassName(); var highlighted = metric != null && metric.Highlighted ? "highlighted" : null; #line default #line hidden WriteLiteral("\r\n"); #line 11 "..\..\Dashboard\Pages\_BlockMetric.cshtml" if (!string.IsNullOrEmpty(DashboardMetric.Url)) { #line default #line hidden WriteLiteral("\r\n"); #line 26 "..\..\Dashboard\Pages\_BlockMetric.cshtml" } #line default #line hidden } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Breadcrumbs.cs ================================================ using System.Collections.Generic; namespace Hangfire.Dashboard.Pages { partial class Breadcrumbs { public Breadcrumbs(string title, IDictionary items) { Title = title; Items = items; } public string Title { get; } public IDictionary Items { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Breadcrumbs.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard @inherits RazorPage ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Breadcrumbs.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_Breadcrumbs.cshtml" using Hangfire.Dashboard; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class Breadcrumbs : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); WriteLiteral("\r\n
    \r\n
  1. \r\n"); #line 7 "..\..\Dashboard\Pages\_Breadcrumbs.cshtml" foreach (var item in Items) { #line default #line hidden WriteLiteral("
  2. \r\n"); #line 10 "..\..\Dashboard\Pages\_Breadcrumbs.cshtml" } #line default #line hidden WriteLiteral("
  3. "); #line 11 "..\..\Dashboard\Pages\_Breadcrumbs.cshtml" Write(Title); #line default #line hidden WriteLiteral("
  4. \r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_ErrorAlert.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard @inherits RazorPage ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_ErrorAlert.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_ErrorAlert.cshtml" using Hangfire.Dashboard; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class ErrorAlert : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); WriteLiteral("\r\n
\r\n
\r\n \r\n \r\n
\r\n
"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_InlineMetric.cs ================================================ namespace Hangfire.Dashboard.Pages { partial class InlineMetric { public InlineMetric(DashboardMetric dashboardMetric) { DashboardMetric = dashboardMetric; } public DashboardMetric DashboardMetric { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_InlineMetric.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard @inherits RazorPage @{ var metric = DashboardMetric.Func(this); var className = metric == null ? "metric-null" : metric.Style.ToClassName(); var highlighted = metric != null && metric.Highlighted ? "highlighted" : null; } @(metric?.Value) ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_InlineMetric.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_InlineMetric.cshtml" using Hangfire.Dashboard; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class InlineMetric : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 4 "..\..\Dashboard\Pages\_InlineMetric.cshtml" var metric = DashboardMetric.Func(this); var className = metric == null ? "metric-null" : metric.Style.ToClassName(); var highlighted = metric != null && metric.Highlighted ? "highlighted" : null; #line default #line hidden WriteLiteral(""); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Navigation.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard @inherits RazorPage @if (NavigationMenu.Items.Count > 0) { } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Navigation.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_Navigation.cshtml" using Hangfire.Dashboard; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class Navigation : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 4 "..\..\Dashboard\Pages\_Navigation.cshtml" if (NavigationMenu.Items.Count > 0) { #line default #line hidden WriteLiteral(" \r\n"); #line 25 "..\..\Dashboard\Pages\_Navigation.cshtml" } #line default #line hidden } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Paginator.cs ================================================ namespace Hangfire.Dashboard.Pages { partial class Paginator { private readonly Pager _pager; public Paginator(Pager pager) { _pager = pager; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Paginator.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard @using Hangfire.Dashboard.Resources; @inherits RazorPage
@if (_pager.TotalPageCount > 1) {
@foreach (var page in _pager.PagerItems) { switch (page.Type) { case Pager.ItemType.Page: @page.PageIndex break; case Pager.ItemType.NextPage: @Strings.Paginator_Next break; case Pager.ItemType.PrevPage: @Strings.Paginator_Prev break; case Pager.ItemType.MorePage: break; } }
}
@Strings.Paginator_TotalItems: @_pager.TotalRecordCount
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_Paginator.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_Paginator.cshtml" using Hangfire.Dashboard; #line default #line hidden #line 3 "..\..\Dashboard\Pages\_Paginator.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class Paginator : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); WriteLiteral("\r\n"); WriteLiteral("
\r\n"); #line 7 "..\..\Dashboard\Pages\_Paginator.cshtml" if (_pager.TotalPageCount > 1) { #line default #line hidden WriteLiteral("
\r\n"); #line 10 "..\..\Dashboard\Pages\_Paginator.cshtml" foreach (var page in _pager.PagerItems) { switch (page.Type) { case Pager.ItemType.Page: #line default #line hidden WriteLiteral(" \r\n"); #line 18 "..\..\Dashboard\Pages\_Paginator.cshtml" break; case Pager.ItemType.NextPage: #line default #line hidden WriteLiteral(" \r\n"); #line 23 "..\..\Dashboard\Pages\_Paginator.cshtml" break; case Pager.ItemType.PrevPage: #line default #line hidden WriteLiteral(" \r\n"); #line 28 "..\..\Dashboard\Pages\_Paginator.cshtml" break; case Pager.ItemType.MorePage: #line default #line hidden WriteLiteral(" \r\n " + " …\r\n \r\n"); #line 33 "..\..\Dashboard\Pages\_Paginator.cshtml" break; } } #line default #line hidden WriteLiteral("
\r\n"); WriteLiteral("
\r\n"); #line 38 "..\..\Dashboard\Pages\_Paginator.cshtml" } #line default #line hidden WriteLiteral("\r\n
\r\n "); #line 41 "..\..\Dashboard\Pages\_Paginator.cshtml" Write(Strings.Paginator_TotalItems); #line default #line hidden WriteLiteral(": "); #line 41 "..\..\Dashboard\Pages\_Paginator.cshtml" Write(_pager.TotalRecordCount); #line default #line hidden WriteLiteral("\r\n
\r\n
\r\n"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_PerPageSelector.cs ================================================ namespace Hangfire.Dashboard.Pages { partial class PerPageSelector { private readonly Pager _pager; public PerPageSelector(Pager pager) { _pager = pager; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_PerPageSelector.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard.Resources; @inherits Hangfire.Dashboard.RazorPage @if (_pager.TotalPageCount > 1) { }
@foreach (var count in new[] { 10, 20, 50, 100, 500, 1000, 5000 }) { 999 ? "visible-lg" : null)" href="@_pager.RecordsPerPageUrl(count)">@count.ToString("N0") }
================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_PerPageSelector.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_PerPageSelector.cshtml" using Hangfire.Dashboard.Resources; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class PerPageSelector : Hangfire.Dashboard.RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); WriteLiteral("\r\n"); #line 5 "..\..\Dashboard\Pages\_PerPageSelector.cshtml" if (_pager.TotalPageCount > 1) { #line default #line hidden WriteLiteral(" \r\n"); #line 15 "..\..\Dashboard\Pages\_PerPageSelector.cshtml" } #line default #line hidden WriteLiteral("
\r\n"); #line 17 "..\..\Dashboard\Pages\_PerPageSelector.cshtml" foreach (var count in new[] { 10, 20, 50, 100, 500, 1000, 5000 }) { #line default #line hidden WriteLiteral(" \r\n"); #line 21 "..\..\Dashboard\Pages\_PerPageSelector.cshtml" } #line default #line hidden WriteLiteral("
\r\n
\r\n " + "
\r\n " + " "); #line 25 "..\..\Dashboard\Pages\_PerPageSelector.cshtml" Write(Strings.PerPageSelector_ItemsPerPage); #line default #line hidden WriteLiteral(":\r\n
\r\n"); } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_SidebarMenu.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; namespace Hangfire.Dashboard.Pages { partial class SidebarMenu { public SidebarMenu([NotNull] IEnumerable> items) { if (items == null) throw new ArgumentNullException(nameof(items)); Items = items; } public IEnumerable> Items { get; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_SidebarMenu.cshtml ================================================ @* Generator: Template TypeVisibility: Internal GeneratePrettyNames: True TrimLeadingUnderscores : true *@ @using Hangfire.Dashboard @inherits RazorPage @if (Items.Any()) { } ================================================ FILE: src/Hangfire.Core/Dashboard/Pages/_SidebarMenu.cshtml.cs ================================================ #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Hangfire.Dashboard.Pages { using System; using System.Collections.Generic; using System.Linq; using System.Text; #line 2 "..\..\Dashboard\Pages\_SidebarMenu.cshtml" using Hangfire.Dashboard; #line default #line hidden [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] internal partial class SidebarMenu : RazorPage { #line hidden public override void Execute() { WriteLiteral("\r\n"); #line 4 "..\..\Dashboard\Pages\_SidebarMenu.cshtml" if (Items.Any()) { #line default #line hidden WriteLiteral(" \r\n"); #line 21 "..\..\Dashboard\Pages\_SidebarMenu.cshtml" } #line default #line hidden } } } #pragma warning restore 1591 ================================================ FILE: src/Hangfire.Core/Dashboard/RazorPage.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Diagnostics; using System.Net; using System.Text; using Hangfire.Storage; using Hangfire.Storage.Monitoring; namespace Hangfire.Dashboard { public abstract class RazorPage { private Lazy _statisticsLazy; private Lazy> _dateTimeLazy; private readonly StringBuilder _content = new StringBuilder(); private string _body; protected RazorPage() { GenerationTime = Stopwatch.StartNew(); Html = new HtmlHelper(this); } public RazorPage Layout { get; protected set; } public HtmlHelper Html { get; private set; } public UrlHelper Url { get; private set; } public JobStorage Storage => Context.Storage; public string AppPath => Context.Options.AppPath; public DashboardOptions DashboardOptions => Context.Options; public Stopwatch GenerationTime { get; private set; } public DateTime? StorageUtcNow { get { if (_dateTimeLazy == null) throw new InvalidOperationException("Page is not initialized."); return _dateTimeLazy.Value.Item1; } } public DateTime ApplicationUtcNow { get { if (_dateTimeLazy == null) throw new InvalidOperationException("Page is not initialized."); return _dateTimeLazy.Value.Item2; } } public TimeSpan? TimeDifference { get { if (_dateTimeLazy == null) throw new InvalidOperationException("Page is not initialized."); return _dateTimeLazy.Value.Item3; } } public StatisticsDto Statistics { get { if (_statisticsLazy == null) throw new InvalidOperationException("Page is not initialized."); return _statisticsLazy.Value; } } public DashboardContext Context { get; private set; } internal DashboardRequest Request => Context.Request; internal DashboardResponse Response => Context.Response; public string RequestPath => Request.Path; public bool IsReadOnly => Context.IsReadOnly; /// public abstract void Execute(); public string Query(string key) { return Request.GetQuery(key); } public override string ToString() { return TransformText(null); } /// public void Assign(RazorPage parentPage) { Context = parentPage.Context; Url = parentPage.Url; GenerationTime = parentPage.GenerationTime; _statisticsLazy = parentPage._statisticsLazy; _dateTimeLazy = parentPage._dateTimeLazy; } internal void Assign(DashboardContext context) { Context = context; Url = new UrlHelper(context); _statisticsLazy = new Lazy(() => { var monitoring = Storage.GetMonitoringApi(); return monitoring.GetStatistics(); }); _dateTimeLazy = new Lazy>(() => { DateTime? storageUtcNow = null; TimeSpan? difference = null; if (Storage.HasFeature(JobStorageFeatures.Connection.GetUtcDateTime)) { using (var connection = Storage.GetReadOnlyConnection() as JobStorageConnection) { storageUtcNow = connection?.GetUtcDateTime(); } } var applicationUtcNow = DateTime.UtcNow; if (storageUtcNow.HasValue) { difference = applicationUtcNow - storageUtcNow; } return new Tuple(storageUtcNow, applicationUtcNow, difference); }); } /// protected void WriteLiteral(string textToAppend) { if (string.IsNullOrEmpty(textToAppend)) return; _content.Append(textToAppend); } /// protected virtual void Write(object value) { if (value == null) return; var html = value as NonEscapedString; WriteLiteral(html?.ToString() ?? Encode(value.ToString())); } protected virtual object RenderBody() { return new NonEscapedString(_body); } private string TransformText(string body) { _body = body; Execute(); if (Layout != null) { Layout.Assign(this); return Layout.TransformText(_content.ToString()); } return _content.ToString(); } private static string Encode(string text) { return string.IsNullOrEmpty(text) ? string.Empty : WebUtility.HtmlEncode(text); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/RazorPageDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Hangfire.Dashboard { internal sealed class RazorPageDispatcher : IDashboardDispatcher { private readonly Func _pageFunc; public RazorPageDispatcher(Func pageFunc) { _pageFunc = pageFunc; } public Task Dispatch(DashboardContext context) { context.Response.ContentType = "text/html"; var page = _pageFunc(context.UriMatch); page.Assign(context); return context.Response.WriteAsync(page.ToString()); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/RouteCollection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Hangfire.Annotations; namespace Hangfire.Dashboard { [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Public API, can not change in minor versions.")] public class RouteCollection { private readonly List> _dispatchers = new List>(); #if FEATURE_OWIN [Obsolete("Use the Add(string, IDashboardDispatcher) overload instead. Will be removed in 2.0.0.")] public void Add([NotNull] string pathTemplate, [NotNull] IRequestDispatcher dispatcher) { if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (dispatcher == null) throw new ArgumentNullException(nameof(dispatcher)); _dispatchers.Add(new Tuple(pathTemplate, new RequestDispatcherWrapper(dispatcher))); } #endif public void Add([NotNull] string pathTemplate, [NotNull] IDashboardDispatcher dispatcher) { if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (dispatcher == null) throw new ArgumentNullException(nameof(dispatcher)); _dispatchers.Add(new Tuple(pathTemplate, dispatcher)); } public Tuple FindDispatcher(string path) { if (path.Length == 0) path = "/"; else if (path.Length > 1) path = path.TrimEnd('/'); foreach (var dispatcher in _dispatchers) { var pattern = dispatcher.Item1; if (!pattern.StartsWith("^", StringComparison.OrdinalIgnoreCase)) pattern = "^" + pattern; if (!pattern.EndsWith("$", StringComparison.OrdinalIgnoreCase)) pattern += "$"; var match = Regex.Match( path, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromSeconds(1)); if (match.Success) { return new Tuple(dispatcher.Item2, match); } } return null; } } } ================================================ FILE: src/Hangfire.Core/Dashboard/RouteCollectionExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Text.RegularExpressions; using Hangfire.Annotations; using System.ComponentModel; namespace Hangfire.Dashboard { public static class RouteCollectionExtensions { public static void AddRazorPage( [NotNull] this RouteCollection routes, [NotNull] string pathTemplate, [NotNull] Func pageFunc) { if (routes == null) throw new ArgumentNullException(nameof(routes)); if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (pageFunc == null) throw new ArgumentNullException(nameof(pageFunc)); routes.Add(pathTemplate, new RazorPageDispatcher(pageFunc)); } #if FEATURE_OWIN [Obsolete("Use the AddCommand(RouteCollection, string, Func) overload instead. Will be removed in 2.0.0.")] public static void AddCommand( [NotNull] this RouteCollection routes, [NotNull] string pathTemplate, [NotNull] Func command) { if (routes == null) throw new ArgumentNullException(nameof(routes)); if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (command == null) throw new ArgumentNullException(nameof(command)); routes.Add(pathTemplate, new CommandDispatcher(command)); } #endif public static void AddCommand( [NotNull] this RouteCollection routes, [NotNull] string pathTemplate, [NotNull] Func command) { if (routes == null) throw new ArgumentNullException(nameof(routes)); if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (command == null) throw new ArgumentNullException(nameof(command)); routes.Add(pathTemplate, new CommandDispatcher(command)); } #if FEATURE_OWIN [Obsolete("Use the AddBatchCommand(RouteCollection, string, Func) overload instead. Will be removed in 2.0.0.")] public static void AddBatchCommand( [NotNull] this RouteCollection routes, [NotNull] string pathTemplate, [NotNull] Action command) { if (routes == null) throw new ArgumentNullException(nameof(routes)); if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (command == null) throw new ArgumentNullException(nameof(command)); routes.Add(pathTemplate, new BatchCommandDispatcher(command)); } #endif public static void AddBatchCommand( [NotNull] this RouteCollection routes, [NotNull] string pathTemplate, [NotNull] Action command) { if (routes == null) throw new ArgumentNullException(nameof(routes)); if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); if (command == null) throw new ArgumentNullException(nameof(command)); routes.Add(pathTemplate, new BatchCommandDispatcher(command)); } public static void AddClientBatchCommand( this RouteCollection routes, string pathTemplate, [NotNull] Action command) { if (command == null) throw new ArgumentNullException(nameof(command)); routes.AddBatchCommand(pathTemplate, (context, jobId) => { var client = context.GetBackgroundJobClient(); command(client, jobId); }); } public static void AddRecurringBatchCommand( this RouteCollection routes, string pathTemplate, [NotNull] Action command) { if (command == null) throw new ArgumentNullException(nameof(command)); routes.AddBatchCommand(pathTemplate, (context, jobId) => { var manager = context.GetRecurringJobManager(); command(manager, jobId); }); } [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("For binary compatibility only. Use overload with Action instead.")] public static void AddRecurringBatchCommand( this RouteCollection routes, string pathTemplate, [NotNull] Action command) { if (command == null) throw new ArgumentNullException(nameof(command)); routes.AddBatchCommand(pathTemplate, (context, jobId) => { var manager = new RecurringJobManager(context.Storage); command(manager, jobId); }); } } } ================================================ FILE: src/Hangfire.Core/Dashboard/UrlHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; namespace Hangfire.Dashboard { public class UrlHelper { #if FEATURE_OWIN private readonly Microsoft.Owin.OwinContext _owinContext; [Obsolete("Please use UrlHelper(DashboardContext) instead. Will be removed in 2.0.0.")] public UrlHelper([NotNull] IDictionary owinEnvironment) { if (owinEnvironment == null) throw new ArgumentNullException(nameof(owinEnvironment)); _owinContext = new Microsoft.Owin.OwinContext(owinEnvironment); } #endif private readonly DashboardContext _context; public UrlHelper([NotNull] DashboardContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); _context = context; } public string To(string relativePath) { return _context.Options.PrefixPath + ( #if FEATURE_OWIN _owinContext?.Request.PathBase.Value ?? #endif _context.Request.PathBase ) + relativePath; } public string Home() { return To("/"); } public string JobDetails(string jobId) { return To("/jobs/details/" + jobId); } public string LinkToQueues() { return To("/jobs/enqueued"); } public string Queue(string queue) { return To("/jobs/enqueued/" + queue); } } } ================================================ FILE: src/Hangfire.Core/DashboardOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; using Hangfire.Dashboard; namespace Hangfire { public class DashboardOptions { private static readonly IDashboardAuthorizationFilter[] DefaultAuthorization = new[] { new LocalRequestsOnlyAuthorizationFilter() }; private IEnumerable _asyncAuthorization; public DashboardOptions() { AppPath = "/"; PrefixPath = string.Empty; _asyncAuthorization = []; Authorization = DefaultAuthorization; IsReadOnlyFunc = static _ => false; StatsPollingInterval = 2000; DisplayStorageConnectionString = true; DashboardTitle = "Hangfire Dashboard"; DisplayNameFunc = null; DefaultRecordsPerPage = 20; DarkModeEnabled = true; } /// /// The path for the Back To Site link. Set to in order to hide the Back To Site link. /// public string AppPath { get; set; } /// /// The path for the first url prefix link, eg. set "/admin", then url is "{domain}/{PrefixPath}/{hangfire}" /// public string PrefixPath { get; set; } #if FEATURE_OWIN [Obsolete("Please use `Authorization` property instead. Will be removed in 2.0.0.")] public IEnumerable AuthorizationFilters { get; set; } #endif public IEnumerable Authorization { get; set; } public IEnumerable AsyncAuthorization { get => _asyncAuthorization; set { _asyncAuthorization = value; if (ReferenceEquals(Authorization, DefaultAuthorization)) { Authorization = []; } } } public Func IsReadOnlyFunc { get; set; } /// /// The interval the /stats endpoint should be polled with. /// public int StatsPollingInterval { get; set; } public bool DisplayStorageConnectionString { get; set; } /// /// The Title displayed on the dashboard, optionally modify to describe this dashboards purpose. /// public string DashboardTitle { get; set; } /// /// Display name provider for jobs /// public Func DisplayNameFunc { get; set; } public bool IgnoreAntiforgeryToken { get; set; } public ITimeZoneResolver TimeZoneResolver { get; set; } /// /// Gets or sets the default number of records per page. /// public int DefaultRecordsPerPage { get; set; } /// /// Gets or sets whether dark mode support is enabled by including /// or excluding the corresponding CSS files. /// public bool DarkModeEnabled { get; set; } /// /// Optional favicon path /// public string FaviconPath { get; set; } = string.Empty; /// /// Gets or sets the time threshold after which a warning icon will be shown near a job or a /// server, depending on its last reported heartbeat. /// /// /// It should be larger than a configured /// value, to give servers a chance to report it, but is expected to be lower than a configured /// value, since this is a heuristic anyway. /// public TimeSpan ServerPossiblyAbortedThreshold { get; set; } = TimeSpan.FromMinutes(1); } } ================================================ FILE: src/Hangfire.Core/DisableConcurrentExecutionAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Globalization; using System.Linq; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Server; using Newtonsoft.Json; namespace Hangfire { public class DisableConcurrentExecutionAttribute : JobFilterAttribute, IServerFilter { public DisableConcurrentExecutionAttribute(int timeoutInSeconds) { if (timeoutInSeconds < 0) throw new ArgumentException("Timeout argument value should be greater that zero."); TimeoutSec = timeoutInSeconds; } [JsonConstructor] public DisableConcurrentExecutionAttribute(string resource, int timeoutSec) : this(timeoutSec) { Resource = resource; } [CanBeNull] public string Resource { get; } public int TimeoutSec { get; } public void OnPerforming(PerformingContext context) { var resource = GetResource(context.BackgroundJob.Job); var timeout = TimeSpan.FromSeconds(TimeoutSec); var distributedLock = context.Connection.AcquireDistributedLock(resource, timeout); context.Items["DistributedLock"] = distributedLock; } public void OnPerformed(PerformedContext context) { if (!context.Items.TryGetValue("DistributedLock", out var value)) { throw new InvalidOperationException("Can not release a distributed lock: it was not acquired."); } var distributedLock = (IDisposable)value; distributedLock.Dispose(); } private string GetResource(Job job) { if (!String.IsNullOrWhiteSpace(Resource)) { try { return String.Format(CultureInfo.InvariantCulture, Resource, job.Args.ToArray()).ToLowerInvariant(); } catch (Exception ex) { throw new FormatException($"Unable to obtain resource identifier: {ex.Message}"); } } return $"{job.Type.ToGenericTypeString()}.{job.Method.Name}"; } } } ================================================ FILE: src/Hangfire.Core/ExceptionInfo.cs ================================================ // This file is part of Hangfire. // Copyright © 2020 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Text; using Hangfire.Annotations; using Hangfire.Common; using Newtonsoft.Json; namespace Hangfire { public sealed class ExceptionInfo { public ExceptionInfo([NotNull] Exception exception) { if (exception == null) throw new ArgumentNullException(nameof(exception)); Message = exception.Message; Type = TypeHelper.CurrentTypeSerializer(exception.GetType()); if (exception.InnerException != null) { InnerException = new ExceptionInfo(exception.InnerException); } } [JsonConstructor] public ExceptionInfo([NotNull] string type, [CanBeNull] string message, [CanBeNull] ExceptionInfo innerException) { Type = type ?? throw new ArgumentNullException(nameof(type)); Message = message; InnerException = innerException; } [NotNull] [JsonProperty("e")] public string Type { get; } [CanBeNull] [JsonProperty("m", NullValueHandling = NullValueHandling.Ignore)] public string Message { get; } [CanBeNull] [JsonProperty("i", NullValueHandling = NullValueHandling.Ignore)] public ExceptionInfo InnerException { get; } public override string ToString() { var sb = new StringBuilder(); var commaIndex = Type.IndexOf(','); sb.Append(commaIndex >= 0 ? Type.Substring(0, commaIndex) : Type); sb.Append(": "); sb.Append(Message); if (InnerException != null) { sb.Append(" ---> "); sb.AppendLine(InnerException.ToString()); } return sb.ToString(); } } } ================================================ FILE: src/Hangfire.Core/ExceptionTypeHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2022 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire { internal static class ExceptionTypeHelper { #if !NETSTANDARD1_3 private static readonly Type StackOverflowType = typeof(StackOverflowException); #endif private static readonly Type OutOfMemoryType = typeof(OutOfMemoryException); internal static bool IsCatchableExceptionType(this Exception e) { var type = e.GetType(); return #if !NETSTANDARD1_3 type != StackOverflowType && #endif type != OutOfMemoryType; } } } ================================================ FILE: src/Hangfire.Core/FromParameterAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire { [AttributeUsage(AttributeTargets.Parameter)] public class FromParameterAttribute : Attribute { public FromParameterAttribute(string parameterName) { ParameterName = parameterName; } public string ParameterName { get; } } } ================================================ FILE: src/Hangfire.Core/FromResultAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire { [AttributeUsage(AttributeTargets.Parameter)] public sealed class FromResultAttribute : FromParameterAttribute { public FromResultAttribute() : base("AntecedentResult") { } } [AttributeUsage(AttributeTargets.Parameter)] public sealed class FromExceptionAttribute : FromParameterAttribute { public FromExceptionAttribute() : base("AntecedentException") { } } } ================================================ FILE: src/Hangfire.Core/GlobalConfiguration.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . // ReSharper disable InconsistentNaming using System; using System.ComponentModel; using System.Threading; using Hangfire.Annotations; namespace Hangfire { [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Public API, can not touch in minor versions.")] public enum CompatibilityLevel { Version_110 = 110, Version_170 = 170, Version_180 = 180, } public class GlobalConfiguration : IGlobalConfiguration { private static int _compatibilityLevel = (int)CompatibilityLevel.Version_110; public static IGlobalConfiguration Configuration { get; } = new GlobalConfiguration(); internal static CompatibilityLevel CompatibilityLevel { get => (CompatibilityLevel)Volatile.Read(ref _compatibilityLevel); set => Volatile.Write(ref _compatibilityLevel, (int)value); } internal static bool HasCompatibilityLevel(CompatibilityLevel level) { return CompatibilityLevel >= level; } internal GlobalConfiguration() { } } public static class CompatibilityLevelExtensions { public static IGlobalConfiguration SetDataCompatibilityLevel( [NotNull] this IGlobalConfiguration configuration, CompatibilityLevel compatibilityLevel) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); #if !NETSTANDARD1_3 if (!Enum.IsDefined(typeof(CompatibilityLevel), compatibilityLevel)) throw new InvalidEnumArgumentException(nameof(compatibilityLevel), (int) compatibilityLevel, typeof(CompatibilityLevel)); #endif GlobalConfiguration.CompatibilityLevel = compatibilityLevel; return configuration; } } } ================================================ FILE: src/Hangfire.Core/GlobalConfigurationExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.ComponentModel; using System.Globalization; using System.Reflection; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Dashboard; using Hangfire.Dashboard.Pages; using Hangfire.Logging; using Hangfire.Logging.LogProviders; using Newtonsoft.Json; namespace Hangfire { [EditorBrowsable(EditorBrowsableState.Never)] public static class GlobalConfigurationExtensions { public static IGlobalConfiguration UseStorage( [NotNull] this IGlobalConfiguration configuration, [NotNull] TStorage storage) where TStorage : JobStorage { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (storage == null) throw new ArgumentNullException(nameof(storage)); return configuration.Use(storage, static x => JobStorage.Current = x); } public static IGlobalConfiguration WithJobExpirationTimeout( [NotNull] this IGlobalConfiguration configuration, TimeSpan timeout) where TStorage : JobStorage { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); configuration.Entry.JobExpirationTimeout = timeout; return configuration; } public static IGlobalConfiguration UseActivator( [NotNull] this IGlobalConfiguration configuration, [NotNull] TActivator activator) where TActivator : JobActivator { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (activator == null) throw new ArgumentNullException(nameof(activator)); return configuration.Use(activator, static x => JobActivator.Current = x); } public static IGlobalConfiguration UseDefaultActivator( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseActivator(new JobActivator()); } public static IGlobalConfiguration UseLogProvider( [NotNull] this IGlobalConfiguration configuration, [NotNull] TLogProvider provider) where TLogProvider : ILogProvider { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (provider == null) throw new ArgumentNullException(nameof(provider)); return configuration.Use(provider, static x => LogProvider.SetCurrentLogProvider(x)); } /// /// Explicitly disables all the logging in Hangfire. Not recommended to use in a production /// application, because logging provides significant benefits in case of exceptions or other /// problems. But it can be useful for testing-related scenarios. /// public static IGlobalConfiguration UseNoOpLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(LogProvider.NoOpLogProvider.Instance); } public static IGlobalConfiguration UseNLogLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new NLogLogProvider()); } public static IGlobalConfiguration UseColouredConsoleLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new ColouredConsoleLogProvider()); } public static IGlobalConfiguration UseColouredConsoleLogProvider( [NotNull] this IGlobalConfiguration configuration, LogLevel minLevel) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new ColouredConsoleLogProvider(minLevel)); } public static IGlobalConfiguration UseLog4NetLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new Log4NetLogProvider()); } #if !NETSTANDARD1_3 public static IGlobalConfiguration UseElmahLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new ElmahLogProvider()); } public static IGlobalConfiguration UseElmahLogProvider( [NotNull] this IGlobalConfiguration configuration, LogLevel minLevel) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new ElmahLogProvider(minLevel)); } #endif #if !NETSTANDARD1_3 public static IGlobalConfiguration UseEntLibLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new EntLibLogProvider()); } #endif public static IGlobalConfiguration UseSerilogLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new SerilogLogProvider()); } #if !NETSTANDARD1_3 public static IGlobalConfiguration UseLoupeLogProvider( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseLogProvider(new LoupeLogProvider()); } #endif public static IGlobalConfiguration UseFilter( [NotNull] this IGlobalConfiguration configuration, [NotNull] TFilter filter) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (filter == null) throw new ArgumentNullException(nameof(filter)); return configuration.Use(filter, static x => GlobalJobFilters.Filters.Add(x)); } public static IGlobalConfiguration UseFilterProvider( [NotNull] this IGlobalConfiguration configuration, [NotNull] TFilterProvider filterProvider) where TFilterProvider : IJobFilterProvider { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (filterProvider == null) throw new ArgumentNullException(nameof(filterProvider)); return configuration.Use(filterProvider, static x => JobFilterProviders.Providers.Add(x)); } public static IGlobalConfiguration UseDashboardMetric( [NotNull] this IGlobalConfiguration configuration, [NotNull] DashboardMetric metric) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (metric == null) throw new ArgumentNullException(nameof(metric)); DashboardMetrics.AddMetric(metric); HomePage.Metrics.Add(metric); return configuration; } public static IGlobalConfiguration UseDashboardMetrics( [NotNull] this IGlobalConfiguration configuration, [NotNull] params DashboardMetric[] metrics) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (metrics == null) throw new ArgumentNullException(nameof(metrics)); foreach (var metric in metrics) { DashboardMetrics.AddMetric(metric); HomePage.Metrics.Add(metric); } return configuration; } public static IGlobalConfiguration UseJobDetailsRenderer( [NotNull] this IGlobalConfiguration configuration, int order, [NotNull] Func renderer) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (renderer == null) throw new ArgumentNullException(nameof(renderer)); JobDetailsRenderer.AddRenderer(order, renderer); return configuration; } public static IGlobalConfiguration UseTypeResolver( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] Func typeResolver) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); TypeHelper.CurrentTypeResolver = typeResolver; return configuration; } public static IGlobalConfiguration UseTypeSerializer( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] Func typeSerializer) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); TypeHelper.CurrentTypeSerializer = typeSerializer; return configuration; } public static IGlobalConfiguration UseDefaultTypeResolver( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseTypeResolver(null); } public static IGlobalConfiguration UseDefaultTypeSerializer( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseTypeSerializer(null); } public static IGlobalConfiguration UseSimpleAssemblyNameTypeSerializer( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseTypeSerializer(TypeHelper.SimpleAssemblyTypeSerializer); } public static IGlobalConfiguration UseIgnoredAssemblyVersionTypeResolver( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseTypeResolver(TypeHelper.IgnoredAssemblyVersionTypeResolver); } /// /// These settings are used to serialize user data like arguments or parameters. /// You can use with option /// to serialize with specified settings /// public static IGlobalConfiguration UseSerializerSettings( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] JsonSerializerSettings settings) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); SerializationHelper.SetUserSerializerSettings(settings); return configuration; } public static IGlobalConfiguration UseRecommendedSerializerSettings( [NotNull] this IGlobalConfiguration configuration) { return UseRecommendedSerializerSettings(configuration, null); } public static IGlobalConfiguration UseRecommendedSerializerSettings( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] Action settingsConfiguration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); var settings = SerializationHelper.GetInternalSettings(); settingsConfiguration?.Invoke(settings); SerializationHelper.SetUserSerializerSettings(settings); return configuration; } public static IGlobalConfiguration UseResultsInContinuations(this IGlobalConfiguration configuration) { return configuration .UseFilter(new JobParameterInjectionFilter()) .UseFilter(new ContinuationsSupportAttribute(pushResults: true)); } public static IGlobalConfiguration UseMaxLinesInExceptionDetails( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] int? numberOfLines) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (numberOfLines.HasValue && numberOfLines.Value <= 1) throw new ArgumentOutOfRangeException(nameof(numberOfLines), "Must be either a positive value larger than 1; or a null value."); States.FailedState.MaxLinesInExceptionDetails = numberOfLines; return configuration; } public static IGlobalConfiguration UseMaxArgumentSizeToRender( [NotNull] this IGlobalConfiguration configuration, int size) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (size < 0) throw new ArgumentOutOfRangeException(nameof(size), "Must be a positive value"); JobMethodCallRenderer.MaxArgumentToRenderSize = size; return configuration; } public static IGlobalConfiguration UseDefaultCulture( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] CultureInfo culture) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseFilter(new CaptureCultureAttribute(culture?.Name)); } public static IGlobalConfiguration UseDefaultCulture( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] CultureInfo culture, bool captureDefault) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseFilter(new CaptureCultureAttribute(culture?.Name, captureDefault)); } public static IGlobalConfiguration UseDefaultCulture( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] CultureInfo culture, [CanBeNull] CultureInfo uiCulture) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseFilter(new CaptureCultureAttribute(culture?.Name, uiCulture?.Name)); } public static IGlobalConfiguration UseDefaultCulture( [NotNull] this IGlobalConfiguration configuration, [CanBeNull] CultureInfo culture, [CanBeNull] CultureInfo uiCulture, bool captureDefault) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); return configuration.UseFilter(new CaptureCultureAttribute(culture?.Name, uiCulture?.Name, captureDefault)); } public static IGlobalConfiguration UseDashboardStylesheet( [NotNull] this IGlobalConfiguration configuration, [NotNull] Assembly assembly, [NotNull] string resource) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (assembly == null) throw new ArgumentNullException(nameof(assembly)); if (resource == null) throw new ArgumentNullException(nameof(resource)); DashboardRoutes.AddStylesheet(assembly, resource); return configuration; } public static IGlobalConfiguration UseDashboardStylesheetDarkMode( [NotNull] this IGlobalConfiguration configuration, [NotNull] Assembly assembly, [NotNull] string resource) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (assembly == null) throw new ArgumentNullException(nameof(assembly)); if (resource == null) throw new ArgumentNullException(nameof(resource)); DashboardRoutes.AddStylesheetDarkMode(assembly, resource); return configuration; } public static IGlobalConfiguration UseDashboardJavaScript( [NotNull] this IGlobalConfiguration configuration, [NotNull] Assembly assembly, [NotNull] string resource) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (assembly == null) throw new ArgumentNullException(nameof(assembly)); if (resource == null) throw new ArgumentNullException(nameof(resource)); DashboardRoutes.AddJavaScript(assembly, resource); return configuration; } [EditorBrowsable(EditorBrowsableState.Never)] public static IGlobalConfiguration Use( [NotNull] this IGlobalConfiguration configuration, T entry, [NotNull] Action entryAction) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); entryAction(entry); return new ConfigurationEntry(entry); } private sealed class ConfigurationEntry : IGlobalConfiguration { public ConfigurationEntry(T entry) { Entry = entry; } public T Entry { get; } } } } ================================================ FILE: src/Hangfire.Core/GlobalJobFilters.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Common; namespace Hangfire { /// /// Represents the global filter collection. /// public static class GlobalJobFilters { static GlobalJobFilters() { // ReSharper disable once UseObjectOrCollectionInitializer Filters = new JobFilterCollection(); // Filters should be added with the `Add` method call: some // of them indirectly use `GlobalJobFilters.Filters` property, // and it is null, when we are using collection initializer. Filters.Add(new CaptureCultureAttribute()); Filters.Add(new AutomaticRetryAttribute()); Filters.Add(new StatisticsHistoryAttribute()); Filters.Add(new ContinuationsSupportAttribute()); } /// /// Gets the global filter collection. /// public static JobFilterCollection Filters { get; } } } ================================================ FILE: src/Hangfire.Core/GlobalStateHandlers.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using Hangfire.States; namespace Hangfire { public static class GlobalStateHandlers { static GlobalStateHandlers() { Handlers = new List { new SucceededState.Handler(), new ScheduledState.Handler(), new EnqueuedState.Handler(), new DeletedState.Handler(), new AwaitingState.Handler() }; } public static ICollection Handlers { get; } } } ================================================ FILE: src/Hangfire.Core/Hangfire.Core.csproj ================================================  net451;net46;netstandard1.3;netstandard2.0; true Hangfire $(DefineConstants);FEATURE_CRONDESCRIPTOR;FEATURE_OWIN; $(MSBuildProgramFiles32)\..\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe $(MSBuildBinPath)\MSBuild.exe Razor.build PublicResXFileCodeGenerator Strings.Designer.cs Hangfire.Dashboard.Resources True True Strings.resx _BlockMetric.cshtml _BlockMetric.cshtml _Breadcrumbs.cshtml _Breadcrumbs.cshtml _ErrorAlert.cshtml _InlineMetric.cshtml _InlineMetric.cshtml _Navigation.cshtml _Paginator.cshtml _Paginator.cshtml _PerPageSelector.cshtml _PerPageSelector.cshtml _SidebarMenu.cshtml _SidebarMenu.cshtml AwaitingJobsPage.cshtml DeletedJobsPage.cshtml EnqueuedJobsPage.cshtml EnqueuedJobsPage.cshtml FailedJobsPage.cshtml FetchedJobsPage.cshtml FetchedJobsPage.cshtml HomePage.cshtml HomePage.cshtml JobDetailsPage.cshtml JobDetailsPage.cshtml LayoutPage.cshtml LayoutPage.cshtml ProcessingJobsPage.cshtml QueuesPage.cshtml RecurringJobsPage.cshtml RetriesPage.cshtml ScheduledJobsPage.cshtml ServersPage.cshtml SucceededJobs.cshtml ================================================ FILE: src/Hangfire.Core/IBackgroundJobClient.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; namespace Hangfire { /// /// Provides extended methods for creating background jobs and changing /// their states. /// public interface IBackgroundJobClientV2 : IBackgroundJobClient { /// /// Gets a storage associated with the current background job client. /// [NotNull] JobStorage Storage { get; } /// /// Creates a background job within the specified state and given job parameters. /// /// /// Job that should be processed in background. /// Initial state for a background job. /// Job parameters to create. /// Unique identifier of a created background job -or- /// , if it was not created. /// /// is null. /// is null. /// Creation failed due to an exception. [CanBeNull] string Create([NotNull] Job job, [NotNull] IState state, [CanBeNull] IDictionary parameters); } /// /// Provides methods for creating background jobs and changing their states. /// /// /// /// Please see the class for /// details regarding the implementation. /// public interface IBackgroundJobClient { /// /// Creates a new background job in a specified state. /// /// /// Job that should be processed in background. /// Initial state for a background job. /// Unique identifier of a created background job -or- /// , if it was not created. /// /// is null. /// is null. /// Creation failed due to an exception. /// /// /// The interface allows implementations to return /// value for this method when background job creation has been canceled /// by an implementation under the normal circumstances (not due to an /// exception). For example, the class /// contains the property that /// may be used by a client filter to cancel a background job creation. /// /// /// The interface allows implementations to create a background /// job in a state other than specified. The given state instance also /// may be modified. For example, class /// contains public setter for the /// property allowing to choose completely different state by state /// election filters. /// [CanBeNull] string Create([NotNull] Job job, [NotNull] IState state); /// /// Attempts to change a state of a background job with a given /// identifier to a specified one. /// /// /// Identifier of background job, whose state should be changed. /// New state for a background job. /// Expected state assertion, or if unneeded. /// /// , if a given state was applied /// successfully otherwise . /// /// is null. /// is null. /// State change failed due to an exception. /// /// /// If value is not null, state /// change will be performed only if the current state name of a job /// equal to the given value. /// /// The interface allows implementations to change a state of a /// background job to other than specified. The given state instance also /// may be modified. For example, class /// contains public setter for the /// property allowing to choose completely different state by state /// election filters. If a state was changed, /// value will be returned. /// bool ChangeState([NotNull] string jobId, [NotNull] IState state, [CanBeNull] string expectedState); } } ================================================ FILE: src/Hangfire.Core/IGlobalConfiguration.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.ComponentModel; namespace Hangfire { [EditorBrowsable(EditorBrowsableState.Never)] public interface IGlobalConfiguration : IGlobalConfiguration { [EditorBrowsable(EditorBrowsableState.Advanced)] T Entry { get; } } [EditorBrowsable(EditorBrowsableState.Never)] public interface IGlobalConfiguration { } } ================================================ FILE: src/Hangfire.Core/IJobCancellationToken.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Threading; namespace Hangfire { public interface IJobCancellationToken { CancellationToken ShutdownToken { get; } /// /// Throws a OperationCanceledException if /// this token has had cancellation requested. /// /// /// This method provides functionality equivalent to: /// /// if (token.ShutdownToken.IsCancellationRequested) /// throw new OperationCanceledException(token); /// /// /// The token has had cancellation requested. void ThrowIfCancellationRequested(); } } ================================================ FILE: src/Hangfire.Core/IRecurringJobManager.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Annotations; using Hangfire.Common; namespace Hangfire { public interface IRecurringJobManagerV2 : IRecurringJobManager { [NotNull] JobStorage Storage { get; } [CanBeNull] string TriggerJob([NotNull] string recurringJobId); } public interface IRecurringJobManager { void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] Job job, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options); void Trigger([NotNull] string recurringJobId); void RemoveIfExists([NotNull] string recurringJobId); } } ================================================ FILE: src/Hangfire.Core/ITimeZoneResolver.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; namespace Hangfire { public sealed class DefaultTimeZoneResolver : ITimeZoneResolver { public TimeZoneInfo GetTimeZoneById(string timeZoneId) { return TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); } } public interface ITimeZoneResolver { [NotNull] TimeZoneInfo GetTimeZoneById([NotNull] string timeZoneId); } } ================================================ FILE: src/Hangfire.Core/IdempotentCompletionAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Common; using Hangfire.States; namespace Hangfire { public class IdempotentCompletionAttribute : JobFilterAttribute, IElectStateFilter { public IdempotentCompletionAttribute() { Order = 0; } public void OnStateElection(ElectStateContext context) { if (String.IsNullOrEmpty(context.CurrentState)) return; var serializedState = context.GetJobParameter("Completion", allowStale: true); if (!String.IsNullOrEmpty(serializedState)) { if (context.CandidateState is ProcessingState || context.CandidateState.IsFinal) { context.CandidateState = SerializationHelper.Deserialize(serializedState, SerializationOption.TypedInternal); } } else if (context.CandidateState.IsFinal) { context.SetJobParameter("Completion", SerializationHelper.Serialize(context.CandidateState, SerializationOption.TypedInternal)); } } } } ================================================ FILE: src/Hangfire.Core/JobActivator.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Server; namespace Hangfire { public class JobActivator { private static JobActivator _current = new JobActivator(); /// /// Gets or sets the current instance /// that will be used to activate jobs during performance. /// public static JobActivator Current { get { return _current; } set { if (value == null) { throw new ArgumentNullException(nameof(value)); } _current = value; } } public virtual object ActivateJob(Type jobType) { return Activator.CreateInstance(jobType); } [Obsolete("Please implement/use the BeginScope(JobActivatorContext) method instead. Will be removed in 2.0.0.")] public virtual JobActivatorScope BeginScope() { return new SimpleJobActivatorScope(this); } public virtual JobActivatorScope BeginScope(JobActivatorContext context) { #pragma warning disable 618 return BeginScope(); #pragma warning restore 618 } public virtual JobActivatorScope BeginScope(PerformContext context) { return this.BeginScope(new JobActivatorContext(context.Connection, context.BackgroundJob, context.CancellationToken)); } class SimpleJobActivatorScope : JobActivatorScope { private readonly JobActivator _activator; private readonly List _disposables = new List(); public SimpleJobActivatorScope([NotNull] JobActivator activator) { if (activator == null) throw new ArgumentNullException(nameof(activator)); _activator = activator; } public override object Resolve(Type type) { var instance = _activator.ActivateJob(type); var disposable = instance as IDisposable; if (disposable != null) { _disposables.Add(disposable); } return instance; } public override void DisposeScope() { foreach (var disposable in _disposables) { disposable.Dispose(); } } } } } ================================================ FILE: src/Hangfire.Core/JobActivatorContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; namespace Hangfire { public class JobActivatorContext { public JobActivatorContext( [NotNull] IStorageConnection connection, [NotNull] BackgroundJob backgroundJob, [NotNull] IJobCancellationToken cancellationToken) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (backgroundJob == null) throw new ArgumentNullException(nameof(backgroundJob)); if (cancellationToken == null) throw new ArgumentNullException(nameof(cancellationToken)); Connection = connection; BackgroundJob = backgroundJob; CancellationToken = cancellationToken; } [NotNull] public BackgroundJob BackgroundJob { get; } [NotNull] public IJobCancellationToken CancellationToken { get; } [NotNull] public IStorageConnection Connection { get; } public void SetJobParameter([NotNull] string name, object value) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); Connection.SetJobParameter(BackgroundJob.Id, name, SerializationHelper.Serialize(value, SerializationOption.User)); } public T GetJobParameter([NotNull] string name) => GetJobParameter(name, allowStale: false); public T GetJobParameter([NotNull] string name, bool allowStale) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); try { string value; if (allowStale && BackgroundJob.ParametersSnapshot != null) { BackgroundJob.ParametersSnapshot.TryGetValue(name, out value); } else { value = Connection.GetJobParameter(BackgroundJob.Id, name); } return SerializationHelper.Deserialize(value, SerializationOption.User); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new InvalidOperationException( $"Could not get a value of the job parameter `{name}`. See inner exception for details.", ex); } } } } ================================================ FILE: src/Hangfire.Core/JobActivatorScope.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; namespace Hangfire { public abstract class JobActivatorScope : IDisposable { // ReSharper disable once InconsistentNaming private static readonly ThreadLocal _current = new ThreadLocal(trackAllValues: false); protected JobActivatorScope() { _current.Value = this; } public static JobActivatorScope Current => _current.Value; [Obsolete("This property wasn't implemented and will be removed in Hangfire 2.0.0.")] public object InnerScope { get; set; } public abstract object Resolve(Type type); public virtual void DisposeScope() { } public void Dispose() { try { DisposeScope(); } finally { _current.Value = null; } GC.SuppressFinalize(this); } } } ================================================ FILE: src/Hangfire.Core/JobCancellationToken.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Threading; namespace Hangfire { public class JobCancellationToken : IJobCancellationToken { private readonly bool _canceled; public JobCancellationToken(bool canceled) { _canceled = canceled; ShutdownToken = new CancellationToken(canceled); } public CancellationToken ShutdownToken { get; } public static IJobCancellationToken Null => null; /// public void ThrowIfCancellationRequested() { ShutdownToken.ThrowIfCancellationRequested(); } } } ================================================ FILE: src/Hangfire.Core/JobCancellationTokenExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Server; namespace Hangfire { internal static class JobCancellationTokenExtensions { public static bool IsAborted(this IJobCancellationToken jobCancellationToken) { if (jobCancellationToken is ServerJobCancellationToken serverJobCancellationToken) { // for ServerJobCancellationToken we may simply check IsAborted property // to prevent unnecessary creation of the linked CancellationTokenSource return serverJobCancellationToken.IsAborted; } return false; } } } ================================================ FILE: src/Hangfire.Core/JobContinuationOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire { [Flags] public enum JobContinuationOptions { OnAnyFinishedState = 0, OnlyOnSucceededState = 1, OnlyOnDeletedState = 2, } } ================================================ FILE: src/Hangfire.Core/JobDisplayNameAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2018 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Reflection; using System.Resources; using Hangfire.Common; using Hangfire.Dashboard; namespace Hangfire { /// /// Specifies a display name for a job method. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class JobDisplayNameAttribute : Attribute { private static readonly ConcurrentDictionary _resourceManagerCache = new ConcurrentDictionary(); public JobDisplayNameAttribute(string displayName) { if (string.IsNullOrEmpty(displayName)) throw new ArgumentException("Display name is empty", nameof(displayName)); DisplayName = displayName; } /// /// Gets display name for the job. /// public string DisplayName { get; } /// /// Gets or sets resource type to localize string. /// public Type ResourceType { get; set; } public virtual string Format(DashboardContext context, Job job) { var format = DisplayName; if (ResourceType != null) { format = _resourceManagerCache .GetOrAdd(ResourceType, InitResourceManager) .GetString(DisplayName, CultureInfo.CurrentUICulture); if (string.IsNullOrEmpty(format)) { // failed to localize display name string, use it as is format = DisplayName; } } return string.Format(CultureInfo.CurrentCulture, format, job.Args.ToArray()); } private static ResourceManager InitResourceManager(Type type) { var prop = type.GetTypeInfo().GetDeclaredProperty("ResourceManager"); if (prop != null && prop.PropertyType == typeof(ResourceManager) && prop.CanRead && prop.GetMethod.IsStatic) { // use existing resource manager if possible return (ResourceManager)prop.GetValue(null); } return new ResourceManager(type); } } } ================================================ FILE: src/Hangfire.Core/JobParameterInjectionFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Reflection; using Hangfire.Common; using Hangfire.Server; using Hangfire.States; namespace Hangfire { public class JobParameterInjectionFilter : IServerFilter { internal static readonly string DefaultException = "OCE"; public void OnPerforming(PerformingContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var argumentsArray = context.BackgroundJob.Job?.Args as object[]; if (argumentsArray == null) return; var parameters = context.BackgroundJob.Job.Method.GetParameters(); for (var index = 0; index < parameters.Length; index++) { var attribute = parameters[index].GetCustomAttribute(); if (attribute == null) continue; var parameterType = parameters[index].ParameterType; var parameterName = attribute.ParameterName; if (String.IsNullOrEmpty(parameterName)) continue; var serialized = context.Connection.GetJobParameter(context.BackgroundJob.Id, parameterName); if (serialized != null) { object deserialized; if (parameterType == typeof(ExceptionInfo) && DefaultException.Equals(serialized, StringComparison.Ordinal)) { deserialized = new ExceptionInfo(DeletedState.DefaultException); } else { deserialized = SerializationHelper.Deserialize(serialized, parameterType, SerializationOption.User); } argumentsArray[index] = deserialized; } } } public void OnPerformed(PerformedContext context) { } } } ================================================ FILE: src/Hangfire.Core/JobStorage.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using Hangfire.Annotations; using Hangfire.Logging; using Hangfire.Server; using Hangfire.States; using Hangfire.Storage; namespace Hangfire { public abstract class JobStorage { private static readonly object LockObject = new object(); private static JobStorage _current; private TimeSpan _jobExpirationTimeout = TimeSpan.FromDays(1); public static JobStorage Current { get { lock (LockObject) { if (_current == null) { throw new InvalidOperationException( "Current JobStorage instance has not been initialized yet. You must set it before using Hangfire Client or Server API. " + #if NET45 || NET46 "For NET Framework applications please use GlobalConfiguration.UseXXXStorage method, where XXX is the storage type, like `UseSqlServerStorage`." #else "For .NET Core applications please call the `IServiceCollection.AddHangfire` extension method from Hangfire.NetCore or Hangfire.AspNetCore package depending on your application type when configuring the services and ensure service-based APIs are used instead of static ones, like `IBackgroundJobClient` instead of `BackgroundJob` and `IRecurringJobManager` instead of `RecurringJob`." #endif ); } return _current; } } set { lock (LockObject) { _current = value; } } } public TimeSpan JobExpirationTimeout { get { return _jobExpirationTimeout; } set { if (value < TimeSpan.Zero) { throw new ArgumentException("JobStorage.JobExpirationTimeout value should be equal or greater than zero.", nameof(value)); } _jobExpirationTimeout = value; } } public virtual bool LinearizableReads => false; public abstract IMonitoringApi GetMonitoringApi(); public abstract IStorageConnection GetConnection(); public virtual IStorageConnection GetReadOnlyConnection() { return GetConnection(); } #pragma warning disable 618 [Obsolete($"Please use the `{nameof(GetStorageWideProcesses)}` and/or `{nameof(GetServerRequiredProcesses)}` methods instead, and enable `{nameof(JobStorageFeatures)}.{nameof(JobStorageFeatures.ProcessesInsteadOfComponents)}`. Will be removed in 2.0.0.")] public virtual IEnumerable GetComponents() { return Enumerable.Empty(); } #pragma warning restore 618 public virtual IEnumerable GetServerRequiredProcesses() { return Enumerable.Empty(); } public virtual IEnumerable GetStorageWideProcesses() { return Enumerable.Empty(); } public virtual IEnumerable GetStateHandlers() { return Enumerable.Empty(); } public virtual void WriteOptionsToLog(ILog logger) { } public virtual bool HasFeature([NotNull] string featureId) { if (featureId == null) throw new ArgumentNullException(nameof(featureId)); return false; } } } ================================================ FILE: src/Hangfire.Core/LatencyTimeoutAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Common; using Hangfire.Logging; using Hangfire.States; namespace Hangfire { /// /// Represents a job filter that automatically deletes a background job, /// when a certain amount of time elapsed since its creation. Deletion /// is taking place when a attempts /// to move a job to the state. /// public sealed class LatencyTimeoutAttribute : JobFilterAttribute, IElectStateFilter { private readonly ILog _logger = LogProvider.For(); /// /// Initializes a new instance of the /// class with the given timeout value. /// /// Non-negative timeout value in seconds /// that will be used to determine whether to delete a job. /// /// /// has a negative value. /// public LatencyTimeoutAttribute(int timeoutInSeconds) { if (timeoutInSeconds < 0) { throw new ArgumentOutOfRangeException(nameof(timeoutInSeconds), "Timeout value must be equal or greater than zero."); } TimeoutInSeconds = timeoutInSeconds; LogLevel = LogLevel.Debug; } /// /// Gets or sets a level for log message that will be produced, when a /// background job was deleted due to exceeded timeout. /// public LogLevel LogLevel { get; set; } public int TimeoutInSeconds { get; } /// public void OnStateElection(ElectStateContext context) { var state = context.CandidateState as ProcessingState; if (state == null) { //this filter only accepts Processing return; } var elapsedTime = DateTime.UtcNow - context.BackgroundJob.CreatedAt; if (elapsedTime.TotalSeconds > TimeoutInSeconds) { context.CandidateState = new DeletedState { Reason = $"Background job has exceeded latency timeout of {TimeoutInSeconds} second(s)" }; _logger.Log( LogLevel, () => $"Background job '{context.BackgroundJob.Id}' has exceeded latency timeout of {TimeoutInSeconds} second(s) and will be deleted"); } } } } ================================================ FILE: src/Hangfire.Core/MisfireHandlingMode.cs ================================================ // This file is part of Hangfire. // Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire { /// /// Specifies how to handle missed schedule when processing server was /// inactive. /// public enum MisfireHandlingMode { /// /// Default mode. Specifies that only a single background job will /// be created, no matter how many occurrences were missed. The "Time" /// parameter for the background job will point to the time background /// job was scheduled. /// Relaxed = 0, /// /// Specifies that new background job will be created for every missed /// occurrence, with "Time" parameter set to the corresponding schedule /// time. /// Strict = 1, /// /// Specifies that no background jobs should be created on missed schedule, /// regardless the number of missed occurrences. /// Ignorable = 2, } } ================================================ FILE: src/Hangfire.Core/MoreLinq/MoreEnumerable.Pairwise.cs ================================================ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects // Copyright (c) 2008 Jonathan Skeet. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #endregion namespace MoreLinq { using System; using System.Collections.Generic; using System.Diagnostics; static partial class MoreEnumerable { /// /// Returns a sequence resulting from applying a function to each /// element in the source sequence and its /// predecessor, with the exception of the first element which is /// only returned as the predecessor of the second element. /// /// The type of the elements of . /// The type of the element of the returned sequence. /// The source sequence. /// A transform function to apply to /// each pair of sequence. /// /// Returns the resulting sequence. /// /// /// This operator uses deferred execution and streams its results. /// /// /// /// int[] numbers = { 123, 456, 789 }; /// IEnumerable<int> result = numbers.Pairwise(5, (a, b) => a + b); /// /// The result variable, when iterated over, will yield /// 579 and 1245, in turn. /// public static IEnumerable Pairwise(this IEnumerable source, Func resultSelector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); return PairwiseImpl(source, resultSelector); } private static IEnumerable PairwiseImpl(this IEnumerable source, Func resultSelector) { Debug.Assert(source != null); Debug.Assert(resultSelector != null); using (var e = source.GetEnumerator()) { if (!e.MoveNext()) yield break; var previous = e.Current; while (e.MoveNext()) { yield return resultSelector(previous, e.Current); previous = e.Current; } } } } } ================================================ FILE: src/Hangfire.Core/Obsolete/BootstrapperConfigurationExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire { /// [Obsolete("Please use `AppBuilderExtensions` class instead. Will be removed in version 2.0.0.")] public static class BootstrapperConfigurationExtensions { /// /// Tells bootstrapper to start a job server with default options /// on application start and stop it automatically on application /// shutdown request. Global job storage is used. /// /// Configuration [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void UseServer(this IBootstrapperConfiguration configuration) { configuration.UseServer(() => new BackgroundJobServer()); } /// /// Tells bootstrapper to start a job server with the given /// amount of workers on application start and stop it automatically /// on application shutdown request. Global job storage is used. /// /// Configuration /// Worker count [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void UseServer( this IBootstrapperConfiguration configuration, int workerCount) { var options = new BackgroundJobServerOptions { WorkerCount = workerCount }; configuration.UseServer(() => new BackgroundJobServer(options)); } /// /// Tells bootstrapper to start a job server with the given /// queues array on application start and stop it automatically /// on application shutdown request. Global job storage is used. /// /// Configuration /// Queues to listen [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void UseServer( this IBootstrapperConfiguration configuration, params string[] queues) { var options = new BackgroundJobServerOptions { Queues = queues }; configuration.UseServer(() => new BackgroundJobServer(options)); } /// /// Tells bootstrapper to start a job server with the given /// queues array and specified amount of workers on application /// start and stop it automatically on application shutdown request. /// Global job storage is used. /// /// Configuration /// Worker count /// Queues to listen [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void UseServer( this IBootstrapperConfiguration configuration, int workerCount, params string[] queues) { var options = new BackgroundJobServerOptions { WorkerCount = workerCount, Queues = queues }; configuration.UseServer(() => new BackgroundJobServer(options)); } /// /// Tells bootstrapper to start a job server with the given /// options on application start and stop it automatically /// on application shutdown request. Global job storage is used. /// /// Configuration /// Job server options [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void UseServer( this IBootstrapperConfiguration configuration, BackgroundJobServerOptions options) { configuration.UseServer(() => new BackgroundJobServer(options)); } /// /// Tells bootstrapper to start a job server, that uses /// the given job storage, on application start and stop /// it automatically on application shutdown request. /// /// Configuration /// Job storage to use [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void UseServer( this IBootstrapperConfiguration configuration, JobStorage storage) { configuration.UseServer(() => new BackgroundJobServer( new BackgroundJobServerOptions(), storage)); } /// /// Tells bootstrapper to start a job server with the given /// options that use the specified storage (not the global one) on /// application start and stop it automatically on application /// shutdown request. /// /// Configuration /// Job storage to use /// Job server options [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void UseServer( this IBootstrapperConfiguration configuration, JobStorage storage, BackgroundJobServerOptions options) { configuration.UseServer(() => new BackgroundJobServer(options, storage)); } } } ================================================ FILE: src/Hangfire.Core/Obsolete/CreateJobFailedException.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; // ReSharper disable once CheckNamespace namespace Hangfire.Client { /// /// The exception that is thrown when a class instance /// could not create a job due to another exception was thrown. /// [Obsolete("Please use the `BackgroundJobClientException` instead. Will be removed in 2.0.0.")] #if !NETSTANDARD1_3 [Serializable] #endif public class CreateJobFailedException : Exception { /// /// Initializes a new instance of the /// class with a specified error message and a reference to the /// inner exception that is the cause of this exception. /// /// The error message that explains the reason for the exception. /// The exception that is the cause of this exception, not null. public CreateJobFailedException(string message, Exception inner) : base(message, inner) { } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected CreateJobFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif } } ================================================ FILE: src/Hangfire.Core/Obsolete/DashboardMiddleware.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Hangfire.Annotations; using Microsoft.Owin; #pragma warning disable 618 // ReSharper disable once CheckNamespace namespace Hangfire.Dashboard { internal sealed class DashboardMiddleware : OwinMiddleware { private readonly string _appPath; private readonly int _statsPollingInterval; private readonly JobStorage _storage; private readonly RouteCollection _routes; private readonly IEnumerable _authorizationFilters; public DashboardMiddleware( OwinMiddleware next, string appPath, int statsPollingInterval, [NotNull] JobStorage storage, [NotNull] RouteCollection routes, [NotNull] IEnumerable authorizationFilters) : base(next) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (routes == null) throw new ArgumentNullException(nameof(routes)); if (authorizationFilters == null) throw new ArgumentNullException(nameof(authorizationFilters)); _appPath = appPath; _statsPollingInterval = statsPollingInterval; _storage = storage; _routes = routes; _authorizationFilters = authorizationFilters; } public override Task Invoke(IOwinContext owinContext) { var dispatcher = _routes.FindDispatcher(owinContext.Request.Path.Value); if (dispatcher == null) { return Next.Invoke(owinContext); } // ReSharper disable once LoopCanBeConvertedToQuery foreach (var filter in _authorizationFilters) { if (!filter.Authorize(owinContext.Environment)) { owinContext.Response.StatusCode = (int) HttpStatusCode.Unauthorized; return owinContext.Response.WriteAsync("401 Unauthorized"); } } var context = new OwinDashboardContext( _storage, new DashboardOptions { AppPath = _appPath, StatsPollingInterval = _statsPollingInterval, AuthorizationFilters = _authorizationFilters }, owinContext.Environment); return dispatcher.Item1.Dispatch(context); } } } ================================================ FILE: src/Hangfire.Core/Obsolete/DashboardOwinExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Microsoft.Owin.Infrastructure; using Owin; // ReSharper disable once CheckNamespace namespace Hangfire.Dashboard { /// [Obsolete("Please use `IAppBuilder.UseHangfireDashboard` OWIN extension method instead. Will be removed in version 2.0.0.")] public static class DashboardOwinExtensions { internal static readonly IAuthorizationFilter[] DefaultAuthorizationFilters = { new LocalRequestsOnlyAuthorizationFilter() }; internal static readonly string DefaultDashboardPath = "/hangfire"; internal static readonly string DefaultAppPath = "/"; /// /// Maps dashboard to the app builder pipeline at "/hangfire" /// with authorization filter that blocks all remote requests /// and storage instance. /// /// The app builder [Obsolete("Please use `IAppBuilder.UseHangfireDashboard` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void MapHangfireDashboard(this IAppBuilder app) { MapHangfireDashboard(app, DefaultDashboardPath, DefaultAppPath); } /// /// Maps dashboard to the app builder pipeline at the specified /// path with authorization filter that blocks all remote requests /// and storage instance. /// /// The app builder /// The path to map dashboard [Obsolete("Please use `IAppBuilder.UseHangfireDashboard` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void MapHangfireDashboard( this IAppBuilder app, string dashboardPath) { MapHangfireDashboard(app, dashboardPath, DefaultAppPath, DefaultAuthorizationFilters); } /// /// Maps dashboard to the app builder pipeline at the specified /// path with authorization filter that blocks all remote requests /// and storage instance. /// /// The app builder /// The path to map dashboard /// The application path on Back To Site link. Pass null in order to hide the Back To Site link. [Obsolete("Please use `IAppBuilder.UseHangfireDashboard` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void MapHangfireDashboard( this IAppBuilder app, string dashboardPath, string appPath) { MapHangfireDashboard(app, dashboardPath, appPath, DefaultAuthorizationFilters); } /// /// Maps dashboard to the app builder pipeline at the specified /// path with given authorization filters that apply to any request /// and storage instance. /// /// The app builder /// The path to map dashboard /// The application path on Back To Site link /// Array of authorization filters [Obsolete("Please use `IAppBuilder.UseHangfireDashboard` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void MapHangfireDashboard( this IAppBuilder app, string dashboardPath, string appPath, IEnumerable authorizationFilters) { MapHangfireDashboard(app, dashboardPath, appPath, authorizationFilters, JobStorage.Current); } /// /// Maps dashboard to the app builder pipeline at the specified path /// with given authorization filters that apply to any request and /// storage instance that is used to query the information. /// /// The app builder /// The path to map dashboard /// The application path on Back To Site link /// Array of authorization filters /// The storage instance [Obsolete("Please use `IAppBuilder.UseHangfireDashboard` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void MapHangfireDashboard( [NotNull] this IAppBuilder app, string dashboardPath, string appPath, IEnumerable authorizationFilters, JobStorage storage) { if (app == null) throw new ArgumentNullException(nameof(app)); SignatureConversions.AddConversions(app); app.Map(dashboardPath, subApp => subApp.Use( appPath, storage, DashboardRoutes.Routes, authorizationFilters)); } } } ================================================ FILE: src/Hangfire.Core/Obsolete/IAuthorizationFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; // ReSharper disable once CheckNamespace namespace Hangfire.Dashboard { [Obsolete("Please use `IDashboardAuthorizationFilter` instead. Will be removed in 2.0.0.")] public interface IAuthorizationFilter { bool Authorize([NotNull] IDictionary owinEnvironment); } } ================================================ FILE: src/Hangfire.Core/Obsolete/IBootstrapperConfiguration.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Dashboard; namespace Hangfire { /// /// /// Represents a configuration class for Hangfire components that /// is used by the class. /// [Obsolete("Please use `GlobalConfiguration` class instead. Will be removed in version 2.0.0.")] public interface IBootstrapperConfiguration { /// /// Tells bootstrapper to pass the given collection of filters /// to the dashboard middleware to authorize dashboard requests. /// Previous calls to this method are ignored. Empty array /// enables access for all users. /// /// Authorization filters [Obsolete("Please use `IAppBuilder.UseHangfireDashboard(\"/hangfire\", new DashboardOptions { AuthorizationFilters = filters })` OWIN extension method instead. Will be removed in version 2.0.0.")] void UseAuthorizationFilters(params IAuthorizationFilter[] filters); /// /// Tells bootstrapper to register the given job filter globally. /// /// Job filter instance [Obsolete("Please use `GlobalConfiguration.UseFilter` instead. Will be removed in version 2.0.0.")] void UseFilter(object filter); /// /// Tells bootstrapper to map the dashboard middleware to the /// given path in the OWIN pipeline. /// /// Dashboard path, '/hangfire' by default [Obsolete("Please use `IAppBuilder.UseHangfireDashboard(string pathMatch)` OWIN extension method instead. Will be removed in version 2.0.0.")] void UseDashboardPath(string path); /// /// Tells bootstrapper to use the given path on Back To Site link in the dashboard. /// /// Back To Site path, '/' by default [Obsolete("Please use `IAppBuilder.UseHangfireDashboard(\"/hangfire\", new DashboardOptions { AppPath = path })` OWIN extension method instead. Will be removed in version 2.0.0.")] void UseAppPath(string path); /// /// Tells bootstrapper to register the given instance of the /// class globally. /// /// Job storage [Obsolete("Please use `GlobalConfiguration.UseStorage` instead. Will be removed in version 2.0.0.")] void UseStorage(JobStorage storage); /// /// Tells bootstrapper to register the given instance of the /// class globally. /// /// Job storage [Obsolete("Please use `GlobalConfiguration.UseActivator` instead. Will be removed in version 2.0.0.")] void UseActivator(JobActivator activator); /// /// Tells bootstrapper to start the given job server on application /// start, and stop it automatically on application shutdown request. /// /// Job server [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] void UseServer(Func server); } } ================================================ FILE: src/Hangfire.Core/Obsolete/IRequestDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading.Tasks; // ReSharper disable once CheckNamespace namespace Hangfire.Dashboard { [Obsolete("Use the `IDashboardDispatcher` interface instead. Will be removed in 2.0.0.")] public interface IRequestDispatcher { Task Dispatch(RequestDispatcherContext context); } } ================================================ FILE: src/Hangfire.Core/Obsolete/IServerComponent.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; // ReSharper disable once CheckNamespace namespace Hangfire.Server { /// [Obsolete("Please use the `IBackgroundProcess` interface where you can. Will be removed in 2.0.0.")] public interface IServerComponent : IServerProcess { void Execute(CancellationToken cancellationToken); } } ================================================ FILE: src/Hangfire.Core/Obsolete/IServerProcess.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; // ReSharper disable once CheckNamespace namespace Hangfire.Server { /// [Obsolete("Please use the `IBackgroundProcess` interface where you can. Will be removed in 2.0.0.")] public interface IServerProcess { } } ================================================ FILE: src/Hangfire.Core/Obsolete/Job.Obsolete.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Reflection; using Hangfire.Annotations; using Hangfire.Server; using Hangfire.Storage; // ReSharper disable once CheckNamespace namespace Hangfire.Common { partial class Job { [Obsolete("Please use Job(Type, MethodInfo, object[]) ctor overload instead. Will be removed in 2.0.0.")] public Job([NotNull] Type type, [NotNull] MethodInfo method, [NotNull] string[] arguments) { if (type == null) throw new ArgumentNullException(nameof(type)); if (method == null) throw new ArgumentNullException(nameof(method)); if (arguments == null) throw new ArgumentNullException(nameof(arguments)); Validate(type, nameof(type), method, nameof(method), arguments.Length, nameof(arguments)); Type = type; Method = method; Args = InvocationData.DeserializeArguments(method, arguments); } /// [NotNull] [Obsolete("Please use `Args` property instead to avoid unnecessary serializations/deserializations. Will be deleted in 2.0.0.")] public string[] Arguments => InvocationData.SerializeArguments(Method, Args); /// [Obsolete("This method is deprecated. Please use `CoreBackgroundJobPerformer` or `BackgroundJobPerformer` classes instead. Will be removed in 2.0.0.")] public object Perform(JobActivator activator, IJobCancellationToken cancellationToken) { if (activator == null) throw new ArgumentNullException(nameof(activator)); if (cancellationToken == null) throw new ArgumentNullException(nameof(cancellationToken)); object instance = null; object result; try { if (!Method.IsStatic) { instance = Activate(activator); } var arguments = GetArguments(cancellationToken); result = InvokeMethod(instance, arguments, cancellationToken); } finally { Dispose(instance); } return result; } [Obsolete("Will be removed in 2.0.0")] private object Activate(JobActivator activator) { try { var instance = activator.ActivateJob(Type); if (instance == null) { throw new InvalidOperationException($"JobActivator returned NULL instance of the '{Type}' type."); } return instance; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new JobPerformanceException( "An exception occurred during job activation.", ex); } } [Obsolete("Will be removed in 2.0.0")] private object[] GetArguments(IJobCancellationToken cancellationToken) { try { var parameters = Method.GetParameters(); var result = new List(Args.Count); for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; var argument = Args[i]; object value; if (typeof(IJobCancellationToken).GetTypeInfo().IsAssignableFrom(parameter.ParameterType.GetTypeInfo())) { value = cancellationToken; } else { value = argument; } result.Add(value); } return result.ToArray(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new JobPerformanceException( "An exception occurred during arguments deserialization.", ex); } } [Obsolete("Will be removed in 2.0.0")] private object InvokeMethod(object instance, object[] deserializedArguments, IJobCancellationToken cancellationToken) { try { return Method.Invoke(instance, deserializedArguments); } catch (TargetInvocationException ex) { CoreBackgroundJobPerformer.HandleJobPerformanceException(ex.InnerException, cancellationToken, null); throw; } } [Obsolete("Will be removed in 2.0.0")] private static void Dispose(object instance) { try { var disposable = instance as IDisposable; disposable?.Dispose(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new JobPerformanceException( "Job has been performed, but an exception occurred during disposal.", ex); } } } } ================================================ FILE: src/Hangfire.Core/Obsolete/OwinBootstrapper.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Dashboard; using Hangfire.Server; using Owin; namespace Hangfire { /// [Obsolete("Please use `GlobalConfiguration` class for configuration, or `IAppBuilder.UseHangfireDashboard` and `IAppBuilder.UseHangfireServer` OWIN extension methods instead. Will be removed in version 2.0.0.")] public static class OwinBootstrapper { /// /// Bootstraps Hangfire components using the given configuration /// action and maps Hangfire Dashboard to the app builder pipeline /// at the configured path ('/hangfire' by default). /// /// The app builder /// Configuration action [Obsolete("Please use `GlobalConfiguration` class for configuration, or `IAppBuilder.UseHangfireDashboard` and `IAppBuilder.UseHangfireServer` OWIN extension methods instead. Will be removed in version 2.0.0.")] public static void UseHangfire( [NotNull] this IAppBuilder app, [NotNull] Action configurationAction) { if (app == null) throw new ArgumentNullException(nameof(app)); if (configurationAction == null) throw new ArgumentNullException(nameof(configurationAction)); var configuration = new BootstrapperConfiguration(); configurationAction(configuration); if (configuration.Activator != null) { JobActivator.Current = configuration.Activator; } if (configuration.Storage != null) { JobStorage.Current = configuration.Storage; } foreach (var filter in configuration.Filters) { GlobalJobFilters.Filters.Add(filter); } foreach (var server in configuration.Servers) { app.RunHangfireServer(server()); } app.MapHangfireDashboard(configuration.DashboardPath, configuration.AppPath, configuration.AuthorizationFilters); } } } ================================================ FILE: src/Hangfire.Core/Obsolete/RequestDispatcherContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Hangfire.Annotations; // ReSharper disable once CheckNamespace namespace Hangfire.Dashboard { [Obsolete("Use the `DashboardContext` class instead. Will be removed in 2.0.0.")] public class RequestDispatcherContext { public RequestDispatcherContext( string appPath, int statsPollingInterval, [NotNull] JobStorage jobStorage, [NotNull] IDictionary owinEnvironment, [NotNull] Match uriMatch) { if (jobStorage == null) throw new ArgumentNullException(nameof(jobStorage)); if (owinEnvironment == null) throw new ArgumentNullException(nameof(owinEnvironment)); if (uriMatch == null) throw new ArgumentNullException(nameof(uriMatch)); AppPath = appPath; StatsPollingInterval = statsPollingInterval; JobStorage = jobStorage; OwinEnvironment = owinEnvironment; UriMatch = uriMatch; } public string AppPath { get; } public int StatsPollingInterval { get; } public JobStorage JobStorage { get; } public IDictionary OwinEnvironment { get; } public Match UriMatch { get; } public static RequestDispatcherContext FromDashboardContext([NotNull] DashboardContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var owinContext = context as OwinDashboardContext; if (owinContext == null) { throw new NotSupportedException($"context must be of type '{nameof(OwinDashboardContext)}'"); } return new RequestDispatcherContext( owinContext.Options.AppPath, owinContext.Options.StatsPollingInterval, owinContext.Storage, owinContext.Environment, owinContext.UriMatch); } } } ================================================ FILE: src/Hangfire.Core/Obsolete/RequestDispatcherWrapper.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading.Tasks; using Hangfire.Annotations; // ReSharper disable once CheckNamespace namespace Hangfire.Dashboard { [Obsolete("Use IDashboardDispatcher-based dispatchers instead. Will be removed in 2.0.0.")] public class RequestDispatcherWrapper : IDashboardDispatcher { private readonly IRequestDispatcher _dispatcher; public RequestDispatcherWrapper([NotNull] IRequestDispatcher dispatcher) { if (dispatcher == null) throw new ArgumentNullException(nameof(dispatcher)); _dispatcher = dispatcher; } public Task Dispatch(DashboardContext context) { return _dispatcher.Dispatch(RequestDispatcherContext.FromDashboardContext(context)); } } } ================================================ FILE: src/Hangfire.Core/Obsolete/ServerOwinExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Threading; using Microsoft.Owin; using Owin; // ReSharper disable once CheckNamespace namespace Hangfire.Server { /// [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static class ServerOwinExtensions { // Prevent GC to collect background servers in hosts that do not // support shutdown notifications. private static readonly ConcurrentBag Servers = new ConcurrentBag(); /// /// Starts the specified background job server and registers the call /// to its `Dispose` method at OWIN application's shutdown event. /// /// The app builder /// The background job server to start [Obsolete("Please use `IAppBuilder.UseHangfireServer` OWIN extension method instead. Will be removed in version 2.0.0.")] public static void RunHangfireServer( this IAppBuilder app, BackgroundJobServer server) { Servers.Add(server); server.Start(); var context = new OwinContext(app.Properties); var token = context.Get("host.OnAppDisposing"); if (token != CancellationToken.None) { token.Register(server.Dispose); } } } } ================================================ FILE: src/Hangfire.Core/Obsolete/ServerWatchdogOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; // ReSharper disable once CheckNamespace namespace Hangfire.Server { /// [Obsolete("Please use `BackgroundJobServerOptions` properties instead. Will be removed in 2.0.0.")] public class ServerWatchdogOptions { private TimeSpan _serverTimeout; private TimeSpan _checkInterval; public ServerWatchdogOptions() { ServerTimeout = ServerWatchdog.DefaultServerTimeout; CheckInterval = ServerWatchdog.DefaultCheckInterval; } public TimeSpan ServerTimeout { get { return _serverTimeout; } set { if (value < TimeSpan.Zero || value > ServerWatchdog.MaxServerTimeout) { throw new ArgumentOutOfRangeException(nameof(value), $"ServerTimeout must be either non-negative and equal to or less than {ServerWatchdog.MaxServerTimeout.Hours} hours"); } _serverTimeout = value; } } public TimeSpan CheckInterval { get { return _checkInterval; } set { if (value < TimeSpan.Zero || value > ServerWatchdog.MaxServerCheckInterval) { throw new ArgumentOutOfRangeException(nameof(value), $"CheckInterval must be either non-negative and equal to or less than {ServerWatchdog.MaxServerCheckInterval.Hours} hours"); }; _checkInterval = value; } } } } ================================================ FILE: src/Hangfire.Core/Obsolete/StartupConfiguration.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Dashboard; namespace Hangfire { /// [Obsolete] internal sealed class BootstrapperConfiguration : IBootstrapperConfiguration { public BootstrapperConfiguration() { Servers = new List>(); DashboardPath = DashboardOwinExtensions.DefaultDashboardPath; AppPath = DashboardOwinExtensions.DefaultAppPath; AuthorizationFilters = DashboardOwinExtensions.DefaultAuthorizationFilters; Filters = new List(); } public string DashboardPath { get; private set; } public string AppPath { get; private set; } public JobStorage Storage { get; private set; } public JobActivator Activator { get; private set; } public List> Servers { get; } public IAuthorizationFilter[] AuthorizationFilters { get; private set; } public List Filters { get; } public void UseAuthorizationFilters(params IAuthorizationFilter[] filters) { AuthorizationFilters = filters; } public void UseFilter(object filter) { Filters.Add(filter); } public void UseDashboardPath(string path) { DashboardPath = path; } public void UseAppPath(string path) { AppPath = path; } public void UseStorage(JobStorage storage) { Storage = storage; } public void UseActivator(JobActivator activator) { Activator = activator; } public void UseServer(Func server) { Servers.Add(server); } } } ================================================ FILE: src/Hangfire.Core/Obsolete/StateContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Common; // ReSharper disable once CheckNamespace namespace Hangfire.States { /// [Obsolete("This class is here for compatibility reasons. Will be removed in 2.0.0.")] public abstract class StateContext { [NotNull] public abstract BackgroundJob BackgroundJob { get; } [NotNull] [Obsolete("Please use BackgroundJob property instead. Will be removed in 2.0.0.")] public string JobId => BackgroundJob.Id; [CanBeNull] [Obsolete("Please use BackgroundJob property instead. Will be removed in 2.0.0.")] public Job Job => BackgroundJob.Job; [Obsolete("Please use BackgroundJob property instead. Will be removed in 2.0.0.")] public DateTime CreatedAt => BackgroundJob.CreatedAt; } } ================================================ FILE: src/Hangfire.Core/Processing/AppDomainUnloadMonitor.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . #if !NETSTANDARD1_3 using System; using System.Threading; namespace Hangfire.Processing { internal static class AppDomainUnloadMonitor { private static int _initialized; private static bool _isUnloading; public static void EnsureInitialized() { if (Interlocked.CompareExchange(ref _initialized, 1, 0) == 0) { AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; AppDomain.CurrentDomain.ProcessExit += OnDomainUnload; } } public static bool IsUnloading => Volatile.Read(ref _isUnloading) || Server.AspNetShutdownDetector.DisposingHttpRuntime; private static void OnDomainUnload(object sender, EventArgs args) { Volatile.Write(ref _isUnloading, true); } } } #endif ================================================ FILE: src/Hangfire.Core/Processing/BackgroundDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; #if !NETSTANDARD1_3 using System.Diagnostics; #endif using System.Linq; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Logging; using ThreadState = System.Threading.ThreadState; namespace Hangfire.Processing { internal sealed class BackgroundDispatcher : IBackgroundDispatcher { private readonly ILog _logger = LogProvider.GetLogger(typeof(BackgroundDispatcher)); private readonly CountdownEvent _stopped; private readonly IBackgroundExecution _execution; private readonly Action _action; private readonly object _state; public BackgroundDispatcher( [NotNull] IBackgroundExecution execution, [NotNull] Action action, [CanBeNull] object state, [NotNull] Func> threadFactory) { if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory)); _execution = execution ?? throw new ArgumentNullException(nameof(execution)); _action = action ?? throw new ArgumentNullException(nameof(action)); _state = state; #if !NETSTANDARD1_3 AppDomainUnloadMonitor.EnsureInitialized(); #endif var threads = threadFactory(DispatchLoop)?.ToArray(); if (threads == null || threads.Length == 0) { throw new ArgumentException("At least one unstarted thread should be created.", nameof(threadFactory)); } if (threads.Any(static thread => thread == null || (thread.ThreadState & ThreadState.Unstarted) == 0)) { throw new ArgumentException("All the threads should be non-null and in the ThreadState.Unstarted state.", nameof(threadFactory)); } _stopped = new CountdownEvent(threads.Length); foreach (var thread in threads) { thread.Start(); } } public bool Wait(TimeSpan timeout) { return _stopped.WaitHandle.WaitOne(timeout); } public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) { await _stopped.WaitHandle.WaitOneAsync(timeout, cancellationToken).ConfigureAwait(false); } public void Dispose() { _execution.Dispose(); _stopped.Dispose(); } public override string ToString() { return _execution.ToString(); } private void DispatchLoop() { try { _execution.Run(_action, _state); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { #if !NETSTANDARD1_3 if (!(ex is ThreadAbortException) || !AppDomainUnloadMonitor.IsUnloading) #endif { try { _logger.FatalException("Dispatcher is stopped due to an exception, you need to restart the server manually. Please report it to Hangfire developers.", ex); } catch (Exception inner) when (inner.IsCatchableExceptionType()) { #if !NETSTANDARD1_3 Debug.WriteLine($"Dispatcher is stopped due to an exception, you need to restart the server manually. Please report it to Hangfire developers: {ex}"); #endif } } } finally { try { _stopped.Signal(); } catch (ObjectDisposedException) { #if !NETSTANDARD1_3 Debug.WriteLine("Unable to signal the stopped event for BackgroundDispatcher: it was already disposed"); #endif } } } } } ================================================ FILE: src/Hangfire.Core/Processing/BackgroundDispatcherAsync.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; #if !NETSTANDARD1_3 using System.Diagnostics; #endif using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Logging; namespace Hangfire.Processing { internal sealed class BackgroundDispatcherAsync : IBackgroundDispatcher { private readonly ILog _logger = LogProvider.GetLogger(typeof(BackgroundDispatcherAsync)); private readonly CountdownEvent _stopped; private readonly IBackgroundExecution _execution; private readonly Func _action; private readonly object _state; private readonly TaskScheduler _taskScheduler; private readonly bool _ownsScheduler; public BackgroundDispatcherAsync( [NotNull] IBackgroundExecution execution, [NotNull] Func action, [CanBeNull] object state, [NotNull] TaskScheduler taskScheduler, int maxConcurrency, bool ownsScheduler) { if (maxConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(maxConcurrency)); _execution = execution ?? throw new ArgumentNullException(nameof(execution)); _action = action ?? throw new ArgumentNullException(nameof(action)); _state = state; _taskScheduler = taskScheduler ?? throw new ArgumentNullException(nameof(taskScheduler)); _ownsScheduler = ownsScheduler; #if !NETSTANDARD1_3 AppDomainUnloadMonitor.EnsureInitialized(); #endif _stopped = new CountdownEvent(maxConcurrency); for (var i = 0; i < maxConcurrency; i++) { Task.Factory.StartNew( DispatchLoop, CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Unwrap(); } } public bool Wait(TimeSpan timeout) { return _stopped.WaitHandle.WaitOne(timeout); } public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) { await _stopped.WaitHandle.WaitOneAsync(timeout, cancellationToken).ConfigureAwait(false); } public void Dispose() { _execution.Dispose(); _stopped.Dispose(); if (_ownsScheduler && _taskScheduler is IDisposable disposableScheduler) { disposableScheduler.Dispose(); } } public override string ToString() { return _execution.ToString(); } private async Task DispatchLoop() { try { await _execution.RunAsync(_action, _state).ConfigureAwait(true); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { #if !NETSTANDARD1_3 if (!(ex is ThreadAbortException) || !AppDomainUnloadMonitor.IsUnloading) #endif { try { _logger.FatalException("Dispatcher is stopped due to an exception, you need to restart the server manually. Please report it to Hangfire developers.", ex); } catch (Exception inner) when (inner.IsCatchableExceptionType()) { #if !NETSTANDARD1_3 Debug.WriteLine($"Dispatcher is stopped due to an exception, you need to restart the server manually. Please report it to Hangfire developers: {ex}"); #endif } } } finally { try { _stopped.Signal(); } catch (ObjectDisposedException) { #if !NETSTANDARD1_3 Debug.WriteLine("Unable to signal the stopped event for BackgroundDispatcher: it was already disposed"); #endif } } } } } ================================================ FILE: src/Hangfire.Core/Processing/BackgroundExecution.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Logging; using ThreadState = System.Threading.ThreadState; namespace Hangfire.Processing { internal sealed class BackgroundExecution : IBackgroundExecution { // This fallback strategy is used for defensive purposes, when there are // problems with obtaining retry delays we should not under any circumstances // fall into constant immediate retries, consuming 100% of CPU. private static readonly TimeSpan FallbackRetryDelay = TimeSpan.FromSeconds(5); // Execution can be in one of three states: Running, Faulted or Failed. Each // one defines its own logging rules to lower the number of logged messages, // to not to make stress on logging subsystem with thousands of messages in // case of transient faults. private readonly ManualResetEvent _stopped = new ManualResetEvent(false); private Stopwatch _faultedSince; private Stopwatch _failedSince; private Stopwatch _lastException; private int _exceptionsCount; private CancellationToken _stopToken; private readonly BackgroundExecutionOptions _options; private readonly ILog _logger; private readonly Stopwatch _createdAt; private Stopwatch _stoppedAt; private CancellationTokenRegistration _stopRegistration; private volatile bool _disposed; public BackgroundExecution([NotNull] BackgroundExecutionOptions options, CancellationToken stopToken) { _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = LogProvider.GetLogger(GetType()); _createdAt = Stopwatch.StartNew(); _stopToken = stopToken; _stopRegistration = _stopToken.Register(SetStoppedAt); #if !NETSTANDARD1_3 AppDomainUnloadMonitor.EnsureInitialized(); #endif } public bool StopRequested => _disposed || _stopToken.IsCancellationRequested; public void Run(Action callback, object state) { if (callback == null) throw new ArgumentNullException(nameof(callback)); var executionId = Guid.NewGuid(); // ExecutionId is a custom correlation id for logging purposes. We can use Thread's // ManagedThreadId property here, but it's better to have a single implementation between // sync and async dispatchers - async one can execute related tasks on different threads, // so ManagedThreadId doesn't work there. //using (LogProvider.OpenMappedContext("ExecutionId", executionId.ToString())) { #if !NETSTANDARD1_3 try #endif { HandleStarted(executionId, out var nextDelay); // There should be no operations between the `while` and `try` blocks to // avoid unintended stopping due to ThreadAbortException between the loop // iterations. Even loop condition is placed into the `try` block. while (true) { // Don't place anything here. try { // All possible exceptions should be handled inside this try/catch // block, including ThreadAbortException (ResetAbort is called when // possible) and ThreadInterruptedException. The loop could only be // interrupted by the corresponding cancellation token, or thread // abort, caused by appdomain unload. // Don't use ThrowIfCancellationRequested here, because it may cause // infinite looping when ThreadAbortException is raised during app // domain unloads. if (StopRequested) break; if (nextDelay != TimeSpan.Zero) { if (!HandleDelay(executionId, nextDelay)) { // Inability to handle the delay means that execution was // already stopped, so we should break the loop. break; } } callback(executionId, state); HandleSuccess(out nextDelay); } #if !NETSTANDARD1_3 catch (ThreadAbortException) when (AppDomainUnloadMonitor.IsUnloading) { // Our thread is aborted due to AppDomain unload. It's better to give up to // not to cause the host to be more aggressive. throw; } #endif catch (OperationCanceledException ex) when (ex.CancellationToken.Equals(_stopToken) || StopRequested) { // We are catching general OCE exception without checking its CancellationToken // property, because the concrete token may be different than our one, for // example when using linked token sources. When we get OCE and our stop token // is canceled, we can simply break the execution loop, because it will be // broken on next iteration anyway. break; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { HandleException(executionId, ex, out nextDelay); } } HandleStop(executionId); } #if !NETSTANDARD1_3 catch (ThreadAbortException ex) { // This is a rude stop. Since we are handling all the thread aborts // inside the loop, only appdomain unload can bring us there. In this // case we don't reset thread abort. HandleThreadAbort(executionId, ex); } #endif } } public async Task RunAsync(Func callback, object state) { if (callback == null) throw new ArgumentNullException(nameof(callback)); var executionId = Guid.NewGuid(); // We can't use Thread.ManagedThreadId to provide a correlation id for logging subsystem, // because task and its continuations can run in different threads due to work stealing // nature of fetching. So instead we are using a custom identifier, hoping OpenMappedContext // implementation uses AsyncLocal instead of ThreadLocal ;-) //using (LogProvider.OpenMappedContext("ExecutionId", executionId.ToString())) { #if !NETSTANDARD1_3 try #endif { HandleStarted(executionId, out var nextDelay); // There should be no operations between the `while` and `try` blocks to // avoid unintended stopping due to ThreadAbortException between the loop // iterations. Even loop condition is placed into the `try` block. while (true) { // Don't place anything here. try { // All possible exceptions should be handled inside this try/catch // block, including ThreadAbortException (ResetAbort is called when // possible) and ThreadInterruptedException. The loop could only be // interrupted by the corresponding cancellation token, or thread // abort, caused by appdomain unload. // Don't use ThrowIfCancellationRequested here, because it may cause // infinite looping when ThreadAbortException is raised during app // domain unloads. if (StopRequested) break; if (nextDelay != TimeSpan.Zero) { if (!await HandleDelayAsync(executionId, nextDelay).ConfigureAwait(true)) { // Inability to handle the delay means that execution was // already stopped, so we should break the loop. break; } } await callback(executionId, state).ConfigureAwait(true); HandleSuccess(out nextDelay); } #if !NETSTANDARD1_3 catch (ThreadAbortException) when (AppDomainUnloadMonitor.IsUnloading) { // Our previous task was aborted due to AppDomain unload. It's better to // give up to not to cause the host to be more aggressive. throw; } #endif catch (OperationCanceledException ex) when (ex.CancellationToken.Equals(_stopToken) || StopRequested) { // We are catching general OCE exception without checking its CancellationToken // property, because the concrete token may be different than our one, for // example when using linked token sources. When we get OCE and our stop token // is canceled, we can simply break the execution loop, because it will be // broken on next iteration anyway. break; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { HandleException(executionId, ex, out nextDelay); } } HandleStop(executionId); } #if !NETSTANDARD1_3 catch (ThreadAbortException ex) { // This is a rude stop. Since we are handling all the thread aborts // inside the loop, only appdomain unload can bring us there. In this // case we don't reset thread abort. HandleThreadAbort(executionId, ex); } #endif } } public void Dispose() { lock (_stopped) { if (_disposed) return; _disposed = true; _stopRegistration.Dispose(); _stopped.Set(); _stopped.Dispose(); } } public void NotifySucceeded() { if (StopRequested) return; // This is an optimization to avoid lock acquisitions on every // loop iteration. It is possible that value will become null // upon entering the lock, but this race condition is benign. if (Volatile.Read(ref _faultedSince) != null) { ToRunningState(); } } public override string ToString() { return _options?.Name ?? GetType().Name; } private void HandleStarted(Guid executionId, out TimeSpan initialDelay) { _logger.Debug($"{GetExecutionLoopTemplate(executionId)} has started in {_createdAt.Elapsed.TotalMilliseconds} ms"); // Looks weird, but several times I was initializing the nextDelay variable // inside the execution loop by mistake. This lead to immediate looped invocation // with no delays and 100% of CPU consumption on transient exceptions, that is // completely unacceptable. So this is just a defensive technique. initialDelay = TimeSpan.Zero; } private bool HandleDelay(Guid executionId, TimeSpan delay) { try { LogRetry(executionId, delay); return !_stopped.WaitOne(delay, _stopToken); } catch (OperationCanceledException ex) when (ex.CancellationToken.Equals(_stopToken) || StopRequested) { return false; } catch (ObjectDisposedException) { return false; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { LogUnableWait(executionId, delay, ex); return false; } } private async Task HandleDelayAsync(Guid executionId, TimeSpan delay) { try { LogRetry(executionId, delay); return !await _stopped.WaitOneAsync(delay, _stopToken).ConfigureAwait(true); } catch (OperationCanceledException ex) when (ex.CancellationToken.Equals(_stopToken) || StopRequested) { return false; } catch (ObjectDisposedException) { return false; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { LogUnableWait(executionId, delay, ex); return false; } } private void LogUnableWait(Guid executionId, TimeSpan delay, Exception ex) { _logger.FatalException($"{GetExecutionLoopTemplate(executionId)} was unable to wait for '{delay}' delay due to an exception. Execution will be stopped.", ex); } private void LogRetry(Guid executionId, TimeSpan delay) { _logger.Debug($"{GetExecutionLoopTemplate(executionId)} will be retried in {delay}..."); } private void NormalizeDelay(ref TimeSpan retryDelay) { if (retryDelay <= TimeSpan.Zero) { _logger.Warn($"{GetExecutionTemplate()} adjusted the retry delay from {retryDelay} to {FallbackRetryDelay}"); retryDelay = FallbackRetryDelay; } } private void HandleSuccess(out TimeSpan nextDelay) { nextDelay = TimeSpan.Zero; NotifySucceeded(); } private void HandleException(Guid executionId, Exception exception, out TimeSpan delay) { #if !NETSTANDARD1_3 // Normally, there should be no checks for AppDomain unload condition, because we can't // get here on appdomain unloads. But Mono < 5.4 has an issue with Thread.ResetAbort, and // it can prevent appdomain to be unloaded: https://bugzilla.xamarin.com/show_bug.cgi?id=5804. // It's better to reassure this can't happen under all circumstances. if ((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested) != 0 && !AppDomainUnloadMonitor.IsUnloading) { try { Thread.ResetAbort(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // .NET Core doesn't support both Thread.Abort and Thread.ResetAbort methods. // I don't see any possible cases, where thread is aborted, but nevertheless // we shouldn't hide the original exception. _logger.ErrorException($"{GetExecutionLoopTemplate(executionId)} was unable to reset thread abort request due to an exception. Background execution can be prematurely stopped.", ex); } } #endif if (StopRequested) { delay = FallbackRetryDelay; return; } try { // Some code might cache exception object and throw the same instance // from multiple threads, despite it's not recommended to do. However // bad things happen, and we should have some diagnostic tools to // understand what's happened and what was the original exception which // is being modified. if (!exception.Data.Contains("ExecutionId")) { exception.Data.Add("ExecutionId", executionId); } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.WarnException($"Was unable to add the ExecutionId property to the exception object, please see inner exception for details. Original exception: ${exception.GetType()} (${exception.Message})", ex); } ToFailedState(exception, out delay); _logger.DebugException($"{GetExecutionLoopTemplate(executionId)} caught an exception and will be retried in {delay}", exception); } private void HandleStop(Guid executionId) { var stoppedAt = Volatile.Read(ref _stoppedAt); _logger.Debug($"{GetExecutionLoopTemplate(executionId)} stopped in {stoppedAt?.Elapsed.TotalMilliseconds ?? 0} ms"); } #if !NETSTANDARD1_3 private void HandleThreadAbort(Guid executionId, Exception exception) { _logger.WarnException($"{GetExecutionLoopTemplate(executionId)} caught ThreadAbortException, see inner exception for details", exception); } #endif private void ToRunningState() { lock (_stopped) { if (_disposed) return; if (_failedSince != null) { // Since we are moving from the Failed state, one or more error messages were // logged, and we should notify administrators that operations are restored. _logger.Info($"{GetExecutionTemplate()} recovered from the Failed state after {_failedSince?.Elapsed} and is in the Running state now"); } else if (_faultedSince != null) { // We are moving from Faulted state, and there may be no any log messages (unless // DEBUG level is enabled). But since some operations were delayed, we should notify // administrators about this event, if a configured threshold is reached (to not to // log thousands of messages). _logger.Log( _faultedSince.Elapsed > _options.WarningThreshold ? LogLevel.Info : LogLevel.Debug, () => $"{GetExecutionTemplate()} recovered from the Faulted state after {_faultedSince?.Elapsed} and is in the Running state now"); } _exceptionsCount = 0; _faultedSince = null; _failedSince = null; _lastException = null; } } private void ToFailedState(Exception exception, out TimeSpan retryDelay) { lock (_stopped) { retryDelay = FallbackRetryDelay; if (_disposed) return; _exceptionsCount++; _lastException = Stopwatch.StartNew(); var optionsRetryDelay = _options.RetryDelay; if (optionsRetryDelay != null) { retryDelay = optionsRetryDelay(_exceptionsCount); } NormalizeDelay(ref retryDelay); if (_faultedSince == null) { _faultedSince = Stopwatch.StartNew(); // If threshold is zero, we'll go to the Failed state directly and log error anyway. if (_options.ErrorThreshold > TimeSpan.Zero) { _logger.DebugException($"{GetExecutionTemplate()} is in the Faulted state now due to an exception, execution will be retried in no more than {retryDelay}", exception); } } if (_failedSince == null && _faultedSince.Elapsed > _options.ErrorThreshold) { // Transition to Failed state, we should log the error message. _logger.ErrorException($"{GetExecutionTemplate()} is in the Failed state now due to an exception, execution will be retried in no more than {retryDelay}", exception); _failedSince = Stopwatch.StartNew(); } else if (_failedSince != null && _lastException.Elapsed >= _options.StillErrorThreshold) { // Still in the Failed state, we should log the error message as a reminder, // but shouldn't do this too often, especially for short retry intervals. _logger.ErrorException($"{GetExecutionTemplate()} is still in the Failed state for {_failedSince?.Elapsed} due to an exception, will be retried in no more than {retryDelay}", exception); } } } private string GetExecutionLoopTemplate(Guid executionId) { return $"Execution loop {ToString()}:{executionId.ToString().Substring(0, 8)}"; } private string GetExecutionTemplate() { return $"Execution {ToString()}"; } private void SetStoppedAt() { Interlocked.CompareExchange(ref _stoppedAt, Stopwatch.StartNew(), null); } } } ================================================ FILE: src/Hangfire.Core/Processing/BackgroundExecutionOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Processing { internal sealed class BackgroundExecutionOptions { private static readonly TimeSpan DefaultMaxAttemptDelay = TimeSpan.FromMinutes(5); private TimeSpan _warningThreshold; private TimeSpan _errorThreshold; private TimeSpan _stillErrorThreshold; private Func _retryDelay; public BackgroundExecutionOptions() { WarningThreshold = TimeSpan.FromSeconds(5); ErrorThreshold = TimeSpan.FromSeconds(15); StillErrorThreshold = TimeSpan.FromSeconds(60); RetryDelay = GetBackOffMultiplier; } public string Name { get; set; } public TimeSpan WarningThreshold { get { return _warningThreshold; } set { if (value < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(value), "Value should be greater than or equal to TimeSpan.Zero"); _warningThreshold = value; } } public TimeSpan ErrorThreshold { get { return _errorThreshold; } set { if (value < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(value), "Value should be greater than or equal to TimeSpan.Zero"); _errorThreshold = value; } } public TimeSpan StillErrorThreshold { get { return _stillErrorThreshold; } set { if (value < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(value), "Value should be greater than or equal to TimeSpan.Zero"); _stillErrorThreshold = value; } } public Func RetryDelay { get { return _retryDelay; } set { if (value == null) throw new ArgumentNullException(nameof(value)); _retryDelay = value; } } internal static TimeSpan GetBackOffMultiplier(int retryAttemptNumber) { //exponential/random retry back-off. var rand = new Random(Guid.NewGuid().GetHashCode()); var nextTry = rand.Next( (int)Math.Pow(retryAttemptNumber, 2), (int)Math.Pow(retryAttemptNumber, 2) + 1); return TimeSpan.FromSeconds(Math.Min(nextTry, DefaultMaxAttemptDelay.TotalSeconds)); } } } ================================================ FILE: src/Hangfire.Core/Processing/BackgroundTaskScheduler.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using ThreadState = System.Threading.ThreadState; namespace Hangfire.Processing { /// Represents a custom implementation of the that uses /// its own threads to execute -based work items and their continuations. /// The primary purpose of this scheduler is background processing, for other use cases /// consider using the scheduler instead. /// /// You can use this scheduler to offload background tasks to a separate, dedicated /// pool of threads, instead of executing them in ThreadPool's threads. /// Background work items don't usually affect the request/response logic directly, so we /// can afford some additional latency and execute them, when no foreground processing is /// held. This is useful, when you want to minimize your response latencies to their minimum, /// and don't want to allow background processing to affect the foreground one. /// /// It is not possible to offload *all* the work to dedicated threads, because a lot of /// libraries hard-code their usage of the default thread pool in one way or another: by using /// the ConfigureAwait(false) method, by explicitly creating continuations on TaskScheduler.Default, /// or simply by using ThreadPool.QueueUserWorkItem method. So this is a best-effort attempt. /// /// Please note that all unprocessed work items are lost, when the /// method is called or the corresponding AppDomain is unloaded (for example, /// due to process shutdown). In order to survive the process restarts, use different solutions /// with persistence, like Hangfire. /// /// /// public sealed class BackgroundTaskScheduler : TaskScheduler, IDisposable { // Single global queue is used instead of work stealing one for simplified maintenance, // and because it's enough, when dealing with async/await methods, since they rarely // enqueue continuations to local queues (comparing to ContinueWith-based continuations). private readonly ConcurrentQueue _queue = new ConcurrentQueue(); private readonly Thread[] _threads; private readonly HashSet _ourThreadIds; // Regular semaphore is used to perform waits, when there are no tasks to be // processed. It doesn't use any kind of busy waiting to allow "foreground" // schedulers to perform useful work, instead of consuming CPU time by spinning // with the hope of a task arrival. It is used with care with no unnecessary // operations involved. private readonly Semaphore _semaphore; private readonly ManualResetEvent _stopped = new ManualResetEvent(false); private readonly WaitHandle[] _waitHandles; private readonly Action _exceptionHandler; private int _disposed; /// Initializes a new instance of the with /// the number of threads based on the property. /// All the created threads will be started to dispatch /// instances scheduled to run on this scheduler. public BackgroundTaskScheduler() : this(Environment.ProcessorCount) { } /// Initializes a new instance of the with /// the given number of dedicated threads that will be creating using the default thread /// factory. All the created threads will be started to dispatch /// instances scheduled to run on this scheduler. /// The number of dedicated threads will be created. /// is less or equal to zero. public BackgroundTaskScheduler(int threadCount) : this(threadStart => DefaultThreadFactory(threadStart, threadCount), DefaultExceptionHandler) { } /// Initializes a new instance of the /// class with the specified and an optional exception /// handler. All the created threads will be started to dispatch /// instances scheduled to run on this scheduler. /// Callback that creates one or more dedicated threads. /// Optional callback that is invoked when unhandled exception occurs /// in one of the threads. After this event this instance is considered stopped. /// is . /// returned or zero threads. /// returned at least one thread not in the state. public BackgroundTaskScheduler( [NotNull] Func> threadFactory, [CanBeNull] Action exceptionHandler) { if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory)); _exceptionHandler = exceptionHandler; _semaphore = new Semaphore(0, Int32.MaxValue); // Stopped event should always be the first in this array, see the DispatchLoop method. _waitHandles = new WaitHandle[] { _stopped, _semaphore }; #if !NETSTANDARD1_3 AppDomainUnloadMonitor.EnsureInitialized(); #endif _threads = threadFactory(DispatchLoop)?.ToArray(); if (_threads == null || _threads.Length == 0) { throw new ArgumentException("At least one non-started thread should be created.", nameof(threadFactory)); } if (_threads.Any(static thread => thread == null || (thread.ThreadState & ThreadState.Unstarted) == 0)) { throw new ArgumentException("All the threads should be non-null and in the ThreadState.Unstarted state.", nameof(threadFactory)); } foreach (var thread in _threads) { thread.Start(); } _ourThreadIds = new HashSet(_threads.Select(static x => x.ManagedThreadId)); } /// public override int MaximumConcurrencyLevel => _threads.Length; /// Signals all the threads to be stopped and releases all the unmanaged resources. /// This method should be called only when you are uninterested on the corresponding tasks, /// i.e. during AppDomain unloads, process shutdowns, etc. public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 1) { return; } _stopped.Set(); // We don't wait for threads here using Thread.Join method, because we can't // guarantee that all the threads will be stopped after a call to the Dispose // method without introducing an infinite block (they are executing user code, // and there can be anything). // Since the Dispose method is usually called from within a protected region // itself (such as when using the `using` statements), we shouldn't block here, // because this may prevent AppDomain from being unloaded, causing unexpected // behavior for applications. // Since we don't wait until completion, our threads are responsible for preventing // or catching ObjectDisposedException from these wait handles. _stopped.Dispose(); _semaphore.Dispose(); } /// protected override void QueueTask(Task task) { // Both ConcurrentQueue and Semaphore may cause a thread to be blocked. During that // transition, CLR is checking whether there are any Thread Interrupts pending and // throws if any. Any exception thrown from this method will lead to an unobserved // exception posted to ThreadPool due to the internal implementation of TaskScheduler, // and this may bring the whole process down. // To prevent this, WaitHandle.WaitAny is required in the work loop. _queue.Enqueue(task); _semaphore.Release(); } /// protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { // There's no efficient way to dequeue an arbitrary element from concurrent queue. if (taskWasPreviouslyQueued) return false; // This method can be called from other schedulers that want to get some advice // whether or not inline execution of tasks that belongs to the current scheduler. // Since we want to execute as much tasks as possible on our dedicated threads, // we allow to inline only requests from the current scheduler, i.e. just to save // some time, since no queueing will be involved. if (!_ourThreadIds.Contains(Environment.CurrentManagedThreadId)) return false; return TryExecuteTask(task); } /// protected override IEnumerable GetScheduledTasks() { ThrowIfDisposed(); return _queue.ToArray(); } private static IEnumerable DefaultThreadFactory(ThreadStart threadStart, int threadCount) { if (threadCount <= 0) throw new ArgumentOutOfRangeException(nameof(threadCount)); var threads = new Thread[threadCount]; for (var i = 0; i < threadCount; i++) { threads[i] = new Thread(threadStart) { Name = $"BackgroundThread #{i + 1}", IsBackground = true, }; } return threads; } private static void DefaultExceptionHandler(Exception exception) { #if !NETSTANDARD1_3 Trace.WriteLine("An unhandled exception occurred: " + exception); #endif } private void DispatchLoop() { try { // The outer loop is needed to keep threads under our control by catching // TIE and TAE exceptions to prevent their destructive behavior of killing // our threads without need to re-create them as implemented in ThreadPool. // Unfortunately we don't have the same reset mechanisms available, because // they are native and aren't exposed to public, so we'll do this on a best // effort basis. // TODO: Reset Thread.Name, Thread.CurrentCulture and Thread.Priority? // What about ExecutionContext and SynchronizationContext? But we don't have // public APIs to handle this. while (Volatile.Read(ref _disposed) == 0) { Task task = null; try { // Kernel wait is required here, because otherwise any pending thread // interrupt may kill the whole process, because of internal TaskScheduler // implementation. By calling WaitHandle.WaitAny here, we are causing // CLR to check if there are pending interrupts earlier, and in the // place where we can safely handle it. while (WaitHandle.WaitAny(_waitHandles) != 0) { if (_queue.TryDequeue(out task)) { TryExecuteTask(task); } } } catch (ObjectDisposedException) { // There's a benign race, when wait handles are disposed just // before the call to WaitAny. Other methods don't throw exceptions // of this type. Since this is an ordinal shutdown, we can skip // the reporting logic. } #if !NETSTANDARD1_3 catch (Exception ex) when (ex is ThreadAbortException || ex is ThreadInterruptedException) { // We don't have methods like IThreadPoolWorkItem.MarkAborted in public // API, but we should handle non-completed task in case of TIE or TAE, // because there's a high probability to lose continuations. So we're // simply re-queueing it. if (task != null && !task.IsCompleted) { QueueTask(task); } // Normally, there should be no check for AppDomain unload condition, because we can't // get here on appdomain unloads. But Mono < 5.4 has an issue with Thread.ResetAbort, and // it can prevent appdomain to be unloaded: https://bugzilla.xamarin.com/show_bug.cgi?id=5804. // It's better to reassure this can't happen under all circumstances. if ((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested) != 0 && !AppDomainUnloadMonitor.IsUnloading) { try { Thread.ResetAbort(); } catch (PlatformNotSupportedException) { } } } #endif } } #if !NETSTANDARD1_3 catch (ThreadAbortException ex) { // ThreadAbortException is expected during AppDomain unloads, so we // don't call the exception handler in this case. if (!AppDomainUnloadMonitor.IsUnloading) { InvokeUnhandledExceptionHandler(ex); } } #endif catch (Exception ex) when (ex.IsCatchableExceptionType()) { InvokeUnhandledExceptionHandler(ex); } } private void InvokeUnhandledExceptionHandler(Exception exception) { try { var handler = _exceptionHandler; handler?.Invoke(exception); } #if !NETSTANDARD1_3 catch (Exception ex) when (ex.IsCatchableExceptionType()) { Trace.WriteLine("Unexpected exception caught in exception handler itself." + Environment.NewLine + ex); } #else catch { } #endif } private void ThrowIfDisposed() { if (Volatile.Read(ref _disposed) == 1) { throw new ObjectDisposedException(GetType().FullName); } } } } ================================================ FILE: src/Hangfire.Core/Processing/IBackgroundDispatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; using System.Threading.Tasks; namespace Hangfire.Processing { // TODO Replace these methods with WaitHandle property in 2.0. public interface IBackgroundDispatcher : IDisposable { bool Wait(TimeSpan timeout); Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken); } } ================================================ FILE: src/Hangfire.Core/Processing/IBackgroundExecution.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading.Tasks; using Hangfire.Annotations; namespace Hangfire.Processing { public interface IBackgroundExecution : IDisposable { void Run([NotNull] Action callback, [CanBeNull] object state); Task RunAsync([NotNull] Func callback, [CanBeNull] object state); } } ================================================ FILE: src/Hangfire.Core/Processing/InlineSynchronizationContext.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Threading; namespace Hangfire.Processing { internal sealed class InlineSynchronizationContext : SynchronizationContext, IDisposable { private readonly ConcurrentQueue> _queue = new ConcurrentQueue>(); private readonly Semaphore _semaphore = new Semaphore(0, Int32.MaxValue); public WaitHandle WaitHandle => _semaphore; public Tuple Dequeue() { _queue.TryDequeue(out var tuple); return tuple; } public void Dispose() { _semaphore.Dispose(); } public override void Post(SendOrPostCallback callback, object state) { try { _queue.Enqueue(Tuple.Create(callback, state)); _semaphore.Release(); } catch (ObjectDisposedException) { base.Post(callback, state); } } } } ================================================ FILE: src/Hangfire.Core/Processing/TaskExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Diagnostics; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Logging; namespace Hangfire.Processing { internal static class TaskExtensions { private static readonly Type[] EmptyTypes = Type.EmptyTypes; private static readonly WaitHandle InvalidWaitHandleInstance = new InvalidWaitHandle(); public static bool WaitOne([NotNull] this WaitHandle waitHandle, TimeSpan timeout, CancellationToken token) { if (waitHandle == null) throw new ArgumentNullException(nameof(waitHandle)); if (timeout < Timeout.InfiniteTimeSpan) throw new ArgumentOutOfRangeException(nameof(timeout)); token.ThrowIfCancellationRequested(); using var ev = token.GetCancellationEvent(); var waitHandles = new[] { waitHandle, ev.WaitHandle }; var stopwatch = Stopwatch.StartNew(); var waitResult = WaitHandle.WaitAny(waitHandles, timeout); stopwatch.Stop(); var timeoutThreshold = TimeSpan.FromMilliseconds(1000); var elapsedThreshold = TimeSpan.FromMilliseconds(500); var protectionTime = TimeSpan.FromSeconds(1); if (waitResult == 0) { return true; } if (!token.IsCancellationRequested && timeout >= timeoutThreshold && stopwatch.Elapsed < elapsedThreshold) { try { var logger = LogProvider.GetLogger(typeof(TaskExtensions)); logger.Error($"Actual wait time for non-canceled token was '{stopwatch.Elapsed.TotalMilliseconds}' ms instead of '{timeout.TotalMilliseconds}' ms, wait result: {waitResult}, using protective wait. Please report this to Hangfire developers."); } finally { Thread.Sleep(protectionTime); } } token.ThrowIfCancellationRequested(); return false; } public static async Task WaitOneAsync([NotNull] this WaitHandle waitHandle, TimeSpan timeout, CancellationToken token) { if (waitHandle == null) throw new ArgumentNullException(nameof(waitHandle)); if (timeout < Timeout.InfiniteTimeSpan) throw new ArgumentOutOfRangeException(nameof(timeout)); token.ThrowIfCancellationRequested(); if (waitHandle.WaitOne(TimeSpan.Zero)) { return true; } var tcs = CreateCompletionSource(); var registration = ThreadPool.RegisterWaitForSingleObject(waitHandle, CallBack, tcs, timeout, executeOnlyOnce: true); if (token.CanBeCanceled) { token.Register(Callback, Tuple.Create(registration, tcs, token), useSynchronizationContext: false); } return await tcs.Task.ConfigureAwait(false); } public static bool IsTaskLike(this Type type, out Func getTaskFunc) { var typeInfo = type.GetTypeInfo(); // There are no primitive types that behave as Task if (!typeInfo.IsPrimitive) { if (typeof(Task).GetTypeInfo().IsAssignableFrom(typeInfo)) { getTaskFunc = static obj => (Task)obj; return true; } // We are don't relying on GetAwaiter/GetResult methods for ValueTask, // because it's not a valid pattern to use them as written here: // https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/#user-content-valid-consumption-patterns-for-valuetasks // So we're using their `AsTask` method to create a task first, and then // waiting on it. // This method can be replaced with wrapping in a custom awaiter like // in ASP.NET Core, but this step requires using the `await` keyword // to get the result, so can be implemented only in future. if (typeInfo.FullName != null && typeInfo.FullName.StartsWith("System.Threading.Tasks.ValueTask", StringComparison.Ordinal)) { var asTask = type.GetRuntimeMethod("AsTask", EmptyTypes); if (asTask != null && asTask.IsPublic && !asTask.IsStatic && typeof(Task).GetTypeInfo().IsAssignableFrom(asTask.ReturnType.GetTypeInfo())) { getTaskFunc = obj => (Task) asTask.Invoke(obj, null); return true; } } } getTaskFunc = null; return false; } public static object GetTaskLikeResult([NotNull] this Task task, object obj, Type returnType) { if (task == null) throw new ArgumentNullException(nameof(task)); if (task != obj) { // We shouldn't call GetAwaiter/GetResult on ValueTask directly, because // there may be a race condition as tells us this article: // https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/#user-content-valid-consumption-patterns-for-valuetasks // So we are waiting on task, returned by the AsTask method to ensure it's // completed, before querying for the result. task.GetAwaiter().GetResult(); } // ReturnType is used instead of task.GetType, because we should return `null` result, // when method is returning non-generic Task. However async state machines may use // Task or Task for these cases, and the number of such // void types is pretty high. So it's much safer to call GetAwaiter/GetResult on an // original result object. // Awaitable type must have a public parameterless GetAwaiter instance method, ... var getAwaiter = returnType.GetRuntimeMethod("GetAwaiter", EmptyTypes); if (getAwaiter == null || getAwaiter.IsStatic || !getAwaiter.IsPublic) return null; var awaiterType = getAwaiter.ReturnType; // ... and also have a public parameterless GetResult instance method var getResult = awaiterType.GetRuntimeMethod("GetResult", EmptyTypes); if (getResult == null || getResult.IsStatic || !getResult.IsPublic) return null; var awaiter = getAwaiter.Invoke(obj, null); return getResult.Invoke(awaiter, null); } private static void CallBack(object state, bool timedOut) { // We do call the Unregister method to prevent race condition between // registered wait and cancellation token registration, so can use the // SetResult safely. ((TaskCompletionSource)state).SetResult(!timedOut); } private static void Callback(object state) { // We need to ensure there's no race condition, where wait handle was // set, but callback wasn't fully completed. In this case handle is // acquired, but task is cancelled. var ctx = (Tuple, CancellationToken>)state; ctx.Item1.Unregister(InvalidWaitHandleInstance); TrySetCanceled(ctx.Item2, ctx.Item3); } private static TaskCompletionSource CreateCompletionSource() { return new TaskCompletionSource( #if !NET451 TaskCreationOptions.RunContinuationsAsynchronously #endif ); } private static void TrySetCanceled(TaskCompletionSource source, CancellationToken token) { source.TrySetCanceled( #if !NET451 token #endif ); } private sealed class InvalidWaitHandle : WaitHandle { #if !NETSTANDARD1_3 [Obsolete("Use the SafeWaitHandle property instead.")] public override IntPtr Handle { get { return InvalidHandle; } set { throw new InvalidOperationException(); } } #endif } } } ================================================ FILE: src/Hangfire.Core/Profiling/EmptyProfiler.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Profiling { internal sealed class EmptyProfiler : IProfiler { internal static readonly IProfiler Instance = new EmptyProfiler(); private EmptyProfiler() { } public TResult InvokeMeasured( TInstance instance, Func action, Func messageFunc) { if (action == null) throw new ArgumentNullException(nameof(action)); return action(instance); } } } ================================================ FILE: src/Hangfire.Core/Profiling/IProfiler.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; namespace Hangfire.Profiling { // TODO: Merge this with logging internal interface IProfiler { // TODO: Replace method with some eventId TResult InvokeMeasured( [CanBeNull] TInstance instance, [NotNull, InstantHandle] Func action, [CanBeNull] Func messageFunc = null); } } ================================================ FILE: src/Hangfire.Core/Profiling/ProfilerExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; namespace Hangfire.Profiling { internal static class ProfilerExtensions { public static void InvokeMeasured( [NotNull] this IProfiler profiler, [CanBeNull] TInstance instance, [NotNull] Action action, [CanBeNull] Func messageFunc = null) { if (profiler == null) throw new ArgumentNullException(nameof(profiler)); if (action == null) throw new ArgumentNullException(nameof(action)); profiler.InvokeMeasured(new InstanceAction(instance, action, messageFunc), InvokeAction, MessageCallback); } private static bool InvokeAction(InstanceAction tuple) { tuple.Action(tuple.Instance); return true; } private static string MessageCallback(InstanceAction action) { return action.MessageFunc?.Invoke(action.Instance); } internal struct InstanceAction { public InstanceAction([CanBeNull] TInstance instance, [NotNull] Action action, [CanBeNull] Func messageFunc) { if (action == null) throw new ArgumentNullException(nameof(action)); Instance = instance; Action = action; MessageFunc = messageFunc; } [CanBeNull] public TInstance Instance { get; } [NotNull] public Action Action { get; } [CanBeNull] public Func MessageFunc { get; } public override string ToString() { return Instance?.ToString() ?? typeof(TInstance).ToString(); } } } } ================================================ FILE: src/Hangfire.Core/Profiling/SlowLogProfiler.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Diagnostics; using Hangfire.Logging; namespace Hangfire.Profiling { internal sealed class SlowLogProfiler : IProfiler { private static readonly TimeSpan DefaultThreshold = TimeSpan.FromMinutes(1); private readonly int _thresholdMs; private readonly ILog _logger; public SlowLogProfiler(ILog logger) : this(logger, DefaultThreshold) { } public SlowLogProfiler(ILog logger, TimeSpan threshold) { _thresholdMs = (int)threshold.TotalMilliseconds; _logger = logger; } public TResult InvokeMeasured( TInstance instance, Func action, Func messageFunc = null) { if (action == null) throw new ArgumentNullException(nameof(action)); var started = Environment.TickCount; try { return action(instance); } finally { var elapsed = unchecked(Environment.TickCount - started); if (elapsed >= _thresholdMs) { _logger.Warn($"Slow log: {instance?.ToString() ?? typeof(TInstance).ToString()} performed \"{messageFunc?.Invoke(instance)}\" in {elapsed / 1000} sec"); } } } } } ================================================ FILE: src/Hangfire.Core/Properties/Annotations.cs ================================================ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; #if NETSTANDARD1_3 namespace System.Diagnostics.CodeAnalysis { [Conditional("DEBUG")] // don't bloat release assemblies [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)] internal sealed class ExcludeFromCodeCoverageAttribute : Attribute { } } #endif #pragma warning disable 1591 // ReSharper disable UnusedMember.Global // ReSharper disable UnusedParameter.Local // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable IntroduceOptionalParameters.Global // ReSharper disable MemberCanBeProtected.Global // ReSharper disable InconsistentNaming // ReSharper disable once CheckNamespace namespace Hangfire.Annotations { /// /// Indicates that the value of the marked element could be null sometimes, /// so the check for null is necessary before its usage /// /// /// [CanBeNull] /// public object Test() { return null; } /// /// public void UseTest() /// { /// var p = Test(); /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' /// } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage( AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] public sealed class CanBeNullAttribute : Attribute { } /// /// Indicates that the value of the marked element could never be null /// /// /// [NotNull] /// public object Foo() /// { /// return null; // Warning: Possible 'null' assignment /// } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage( AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] public sealed class NotNullAttribute : Attribute { } /// /// Indicates that the marked method builds string by format pattern and (optional) arguments. /// Parameter, which contains format string, should be given in constructor. The format string /// should be in -like form /// /// /// [StringFormatMethod("message")] /// public void ShowError(string message, params object[] args) { /* do something */ } /// /// public void Foo() /// { /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string /// } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage( AttributeTargets.Constructor | AttributeTargets.Method)] public sealed class StringFormatMethodAttribute : Attribute { /// /// Specifies which parameter of an annotated method should be treated as format-string /// public StringFormatMethodAttribute(string formatParameterName) { FormatParameterName = formatParameterName; } public string FormatParameterName { get; private set; } } /// /// Indicates that the function argument should be string literal and match one /// of the parameters of the caller function. For example, ReSharper annotates /// the parameter of /// /// /// public void Foo(string param) /// { /// if (param == null) /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol /// } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.Parameter)] public sealed class InvokerParameterNameAttribute : Attribute { } /// /// Indicates that the method is contained in a type that implements /// interface /// and this method is used to notify that some property value changed /// /// /// The method should be non-static and conform to one of the supported signatures: /// /// NotifyChanged(string) /// NotifyChanged(params string[]) /// NotifyChanged{T}(Expression{Func{T}}) /// NotifyChanged{T,U}(Expression{Func{T,U}}) /// SetProperty{T}(ref T, T, string) /// /// /// /// public class Foo : INotifyPropertyChanged /// { /// public event PropertyChangedEventHandler PropertyChanged; /// [NotifyPropertyChangedInvocator] /// protected virtual void NotifyChanged(string propertyName) { ... } /// /// private string _name; /// public string Name { /// get { return _name; } /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } /// } /// } /// /// Examples of generated notifications: /// /// NotifyChanged("Property") /// NotifyChanged(() => Property) /// NotifyChanged((VM x) => x.Property) /// SetProperty(ref myField, value, "Property") /// /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.Method)] public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute { public NotifyPropertyChangedInvocatorAttribute() { } public NotifyPropertyChangedInvocatorAttribute(string parameterName) { ParameterName = parameterName; } public string ParameterName { get; private set; } } /// /// Describes dependency between method input and output /// /// ///

Function Definition Table syntax:

/// /// FDT ::= FDTRow [;FDTRow]* /// FDTRow ::= Input => Output | Output <= Input /// Input ::= ParameterName: Value [, Input]* /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} /// Value ::= true | false | null | notnull | canbenull /// /// If method has single input parameter, it's name could be omitted.
/// Using halt (or void/nothing, which is the same) /// for method output means that the methos doesn't return normally.
/// canbenull annotation is only applicable for output parameters.
/// You can use multiple [ContractAnnotation] for each FDT row, /// or use single attribute with rows separated by semicolon.
///
/// /// /// [ContractAnnotation("=> halt")] /// public void TerminationMethod() /// /// /// [ContractAnnotation("halt <= condition: false")] /// public void Assert(bool condition, string text) // regular assertion method /// /// /// [ContractAnnotation("s:null => true")] /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() /// /// /// // A method that returns null if the parameter is null, and not null if the parameter is not null /// [ContractAnnotation("null => null; notnull => notnull")] /// public object Transform(object data) /// /// /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] /// public bool TryParse(string s, out Person result) /// /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class ContractAnnotationAttribute : Attribute { public ContractAnnotationAttribute([NotNull] string contract) : this(contract, false) { } public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) { Contract = contract; ForceFullStates = forceFullStates; } public string Contract { get; private set; } public bool ForceFullStates { get; private set; } } /// /// Indicates that marked element should be localized or not /// /// /// [LocalizationRequiredAttribute(true)] /// public class Foo /// { /// private string str = "my string"; // Warning: Localizable string /// } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.All)] public sealed class LocalizationRequiredAttribute : Attribute { public LocalizationRequiredAttribute() : this(true) { } public LocalizationRequiredAttribute(bool required) { Required = required; } public bool Required { get; private set; } } /// /// Indicates that the value of the marked type (or its derivatives) /// cannot be compared using '==' or '!=' operators and Equals() /// should be used instead. However, using '==' or '!=' for comparison /// with null is always permitted. /// /// /// [CannotApplyEqualityOperator] /// class NoEquality { } /// class UsesNoEquality /// { /// public void Test() /// { /// var ca1 = new NoEquality(); /// var ca2 = new NoEquality(); /// if (ca1 != null) // OK /// { /// bool condition = ca1 == ca2; // Warning /// } /// } /// } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage( AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } /// /// When applied to a target attribute, specifies a requirement for any type marked /// with the target attribute to implement or inherit specific type or types. /// /// /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement /// public class ComponentAttribute : Attribute { } /// [Component] // ComponentAttribute requires implementing IComponent interface /// public class MyComponent : IComponent { } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [BaseTypeRequired(typeof(Attribute))] public sealed class BaseTypeRequiredAttribute : Attribute { public BaseTypeRequiredAttribute([NotNull] Type baseType) { BaseType = baseType; } [NotNull] public Type BaseType { get; private set; } } /// /// Indicates that the marked symbol is used implicitly /// (e.g. via reflection, in external library), so this symbol /// will not be marked as unused (as well as by other usage inspections) /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.All)] public sealed class UsedImplicitlyAttribute : Attribute { public UsedImplicitlyAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) { } public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) { } public UsedImplicitlyAttribute( ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { UseKindFlags = useKindFlags; TargetFlags = targetFlags; } public ImplicitUseKindFlags UseKindFlags { get; private set; } public ImplicitUseTargetFlags TargetFlags { get; private set; } } /// /// Should be used on attributes and causes ReSharper /// to not mark symbols marked with such attributes as unused /// (as well as by other usage inspections) /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.Class)] public sealed class MeansImplicitUseAttribute : Attribute { public MeansImplicitUseAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) : this(useKindFlags, ImplicitUseTargetFlags.Default) { } public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) { } public MeansImplicitUseAttribute( ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { UseKindFlags = useKindFlags; TargetFlags = targetFlags; } [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } } [EditorBrowsable(EditorBrowsableState.Never)] [Flags] [SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Public API, can not change in minor versions.")] public enum ImplicitUseKindFlags { Default = Access | Assign | InstantiatedWithFixedConstructorSignature, /// Only entity marked with attribute considered used Access = 1, /// Indicates implicit assignment to a member Assign = 2, /// /// Indicates implicit instantiation of a type with fixed constructor signature. /// That means any unused constructor parameters won't be reported as such. /// InstantiatedWithFixedConstructorSignature = 4, /// Indicates implicit instantiation of a type InstantiatedNoFixedConstructorSignature = 8, } /// /// Specify what is considered used implicitly /// when marked with /// or /// [EditorBrowsable(EditorBrowsableState.Never)] [Flags] [SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Public API, can not change in minor versions.")] public enum ImplicitUseTargetFlags { Default = Itself, [SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "Public API, can not change in minor versions.")] Itself = 1, /// Members of entity marked with attribute are considered used Members = 2, /// Entity marked with attribute and all its members considered used WithMembers = Itself | Members } /// /// This attribute is intended to mark publicly available API /// which should not be removed and so is treated as used /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.All)] [MeansImplicitUse] public sealed class PublicAPIAttribute : Attribute { public PublicAPIAttribute() : this(String.Empty) { } public PublicAPIAttribute([NotNull] string comment) { Comment = comment; } [NotNull] public string Comment { get; private set; } } /// /// Tells code analysis engine if the parameter is completely handled /// when the invoked method is on stack. If the parameter is a delegate, /// indicates that delegate is executed while the method is executed. /// If the parameter is an enumerable, indicates that it is enumerated /// while the method is executed /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.Parameter)] public sealed class InstantHandleAttribute : Attribute { } /// /// Indicates that a method does not make any observable state changes. /// The same as System.Diagnostics.Contracts.PureAttribute /// /// /// [Pure] private int Multiply(int x, int y) { return x * y; } /// public void Foo() { /// const int a = 2, b = 2; /// Multiply(a, b); // Waring: Return value of pure method is not used /// } /// [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage(AttributeTargets.Method)] public sealed class PureAttribute : Attribute { } [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage( AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] public sealed class HtmlElementAttributesAttribute : Attribute { public HtmlElementAttributesAttribute() : this(String.Empty) { } public HtmlElementAttributesAttribute([NotNull] string name) { Name = name; } [NotNull] public string Name { get; private set; } } [EditorBrowsable(EditorBrowsableState.Never)] [AttributeUsage( AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] public sealed class HtmlAttributeValueAttribute : Attribute { public HtmlAttributeValueAttribute([NotNull] string name) { Name = name; } [NotNull] public string Name { get; private set; } } } ================================================ FILE: src/Hangfire.Core/Properties/AssemblyInfo.cs ================================================ using System; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Hangfire")] [assembly: AssemblyDescription("Core classes of Hangfire that are independent of any framework.")] [assembly: Guid("4deecd4f-19f6-426b-aa87-6cd1a03eaa48")] [assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("Hangfire.Core.Tests")] [assembly: NeutralResourcesLanguage("en")] // Allow the generation of mocks for internal types [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] ================================================ FILE: src/Hangfire.Core/Properties/NamespaceDoc.cs ================================================ // ReSharper disable CheckNamespace using System.Runtime.CompilerServices; namespace Hangfire { /// /// The namespace contains high-level types for configuring, /// creating and processing background jobs, such as , /// and . /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Annotations { /// /// The namespace contains attributes that enable /// additional code inspections in design time with JetBrains ReSharper. /// /// /// To enable annotations, open ReSharper options → Code Inspections → Code Annotations /// and add the namespace to the corresponding list. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Client { /// /// The namespace contains types that allow you to /// customize the background job creation pipeline using the , /// or define your own creation process by implementing the /// interface. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Common { /// /// The namespace provides base types for background /// job filters, such as , and some helper classes. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Dashboard { /// /// The namespace contains types that allow you to /// restrict an access to the Dashboard UI by implementing the /// interface, as well as customize it by adding new pages, menu items, metrics, routes. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Dashboard.Pages { /// /// The namespace contains the /// class, layout for all the Dashboard UI pages. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Logging { /// /// The Hangfire.Logging namespaces contain types that allow you to /// integrate Hangfire's logging with your projects as well as use it /// to log custom messages. /// [CompilerGenerated] class NamespaceGroupDoc { } /// /// The namespace contains types that allow you to /// integrate Hangfire's logging with your projects as well as use it /// to log custom messages. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Logging.LogProviders { /// /// The namespace contains types for /// supporting most popular logging frameworks to simplify the logging integration /// with your projects. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Server { /// /// The namespace contains types that are responsible /// for background processing. You may use them to customize your processing pipeline /// by implementing the interface or define your own /// continuously-running background processes by implementing the /// as well as create completely custom instances of . /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.States { /// /// The namespace contains types that describe /// background job states and the transitions between them. You can implement /// custom or /// to customize the state changing pipeline, or define your own state by /// implementing the interface. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Storage { /// /// The Hangfire.Storage namespaces contain abstract types like , /// and for /// querying and modifying the underlying background job storage. /// These types are also used to implement support for other persistent storages. /// [CompilerGenerated] class NamespaceGroupDoc { } /// /// The Hangfire.Storage namespaces contain abstract types like , /// and for /// querying and modifying the underlying background job storage. /// These types are also used to implement support for other persistent storages. /// [CompilerGenerated] class NamespaceDoc { } } namespace Hangfire.Storage.Monitoring { /// /// The provides data transfer objects /// for the interface. /// /// /// I have no idea why I placed these types to a separate namespace, they should /// be moved to the parent namespace in version 2.0. /// [CompilerGenerated] class NamespaceDoc { } } ================================================ FILE: src/Hangfire.Core/QueueAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Globalization; using System.Linq; using Hangfire.Common; using Hangfire.States; namespace Hangfire { /// /// Represents attribute, that is used to determine queue name /// for background jobs. It can be applied to the methods and classes. /// If the attribute is not applied neither to the method, nor the class, /// then default queue will be used. /// /// /// (x => x.ReportError("Something bad happened")); /// /// // Background job will be placed on the 'critical' queue. /// BackgroundJob.Enqueue(x => x.ReportFatal("Really bad thing!")); /// /// ]]> public sealed class QueueAttribute : JobFilterAttribute, IElectStateFilter { /// /// Initializes a new instance of the class /// using the specified queue name. /// /// Queue name. public QueueAttribute(string queue) { Queue = queue; Order = Int32.MaxValue; } /// /// Gets the queue name that will be used for background jobs. /// public string Queue { get; } public void OnStateElection(ElectStateContext context) { if (context.CandidateState is EnqueuedState enqueuedState) { enqueuedState.Queue = String.Format(CultureInfo.InvariantCulture, Queue, context.BackgroundJob.Job.Args.ToArray()); } } } } ================================================ FILE: src/Hangfire.Core/Razor.build ================================================ Hangfire true $(MsBuildProjectDirectory)\ ================================================ FILE: src/Hangfire.Core/RecurringJob.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.States; namespace Hangfire { public static class RecurringJob { private static readonly Func DefaultFactory = static () => new RecurringJobManager(); private static readonly object ManagerFactoryLock = new object(); private static Func _managerFactory; internal static Func ManagerFactory { get { lock (ManagerFactoryLock) { return _managerFactory ?? DefaultFactory; } } set { lock (ManagerFactoryLock) { _managerFactory = value; } } } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), timeZone, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), options); } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), timeZone, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), options); } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, options); } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, options); } [Obsolete("Please use AddOrUpdate(string, Expression, Func, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use AddOrUpdate(string, Expression>, Func, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use AddOrUpdate(string, Expression, string, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } [Obsolete("Please use AddOrUpdate(string, Expression>, string, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), timeZone, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), options); } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), timeZone, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(methodCall, cronExpression(), options); } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, options); } [Obsolete("Please use an overload with the explicit recurringJobId parameter and RecurringJobOptions instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } [Obsolete("Please use an overload with the explicit recurringJobId parameter instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); var id = GetRecurringJobId(job); ManagerFactory().AddOrUpdate(id, job, cronExpression, options); } [Obsolete("Please use AddOrUpdate(string, Expression>, Func, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use AddOrUpdate(string, Expression>, Func, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use AddOrUpdate(string, Expression>, string, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } [Obsolete("Please use AddOrUpdate(string, Expression>, string, RecurringJobOptions) instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [CanBeNull] TimeZoneInfo timeZone = null, [NotNull] string queue = EnqueuedState.DefaultQueue) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { var job = Job.FromExpression(methodCall); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] string recurringJobId, [NotNull] string queue, [NotNull, InstantHandle] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); ManagerFactory().AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void RemoveIfExists([NotNull] string recurringJobId) { ManagerFactory().RemoveIfExists(recurringJobId); } [Obsolete("Please use the TriggerJob method instead. Will be removed in 2.0.0.")] public static void Trigger([NotNull] string recurringJobId) { ManagerFactory().Trigger(recurringJobId); } public static string TriggerJob([NotNull] string recurringJobId) { return ManagerFactory().TriggerJob(recurringJobId); } private static string GetRecurringJobId(Job job) { return $"{job.Type.ToGenericTypeString()}.{job.Method.Name}"; } } } ================================================ FILE: src/Hangfire.Core/RecurringJobEntity.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Cronos; using Hangfire.Annotations; using Hangfire.Common; namespace Hangfire { internal sealed class RecurringJobEntity { private static readonly char[] SeparatorCharacters = new[] { ' ', '\t' }; private readonly IDictionary _recurringJob; public RecurringJobEntity( [NotNull] string recurringJobId, [NotNull] IDictionary recurringJob) { _recurringJob = recurringJob ?? throw new ArgumentNullException(nameof(recurringJob)); RecurringJobId = recurringJobId ?? throw new ArgumentNullException(nameof(recurringJobId)); if (recurringJob.TryGetValue("Queue", out var queue) && !String.IsNullOrWhiteSpace(queue)) { Queue = queue; } if (recurringJob.TryGetValue("TimeZoneId", out var timeZoneId) && !String.IsNullOrWhiteSpace(timeZoneId)) { TimeZoneId = timeZoneId; } else { TimeZoneId = TimeZoneInfo.Utc.Id; } if (recurringJob.TryGetValue("Cron", out var cron) && !String.IsNullOrWhiteSpace(cron)) { Cron = cron; } if (recurringJob.TryGetValue("Job", out var job) && !String.IsNullOrWhiteSpace(job)) { Job = job; } if (recurringJob.TryGetValue("LastJobId", out var lastJobId) && !String.IsNullOrWhiteSpace(lastJobId)) { LastJobId = lastJobId; } if (recurringJob.TryGetValue("LastExecution", out var lastExecution) && !String.IsNullOrWhiteSpace(lastExecution)) { LastExecution = JobHelper.DeserializeDateTime(lastExecution); } if (recurringJob.TryGetValue("NextExecution", out var nextExecution) && !String.IsNullOrWhiteSpace(nextExecution)) { NextExecution = JobHelper.DeserializeDateTime(nextExecution); } if (recurringJob.TryGetValue("CreatedAt", out var createdAt) && !String.IsNullOrWhiteSpace(createdAt)) { CreatedAt = JobHelper.DeserializeDateTime(createdAt); } if (recurringJob.TryGetValue("Misfire", out var misfireStr)) { MisfireHandling = (MisfireHandlingMode)Enum.Parse(typeof(MisfireHandlingMode), misfireStr); if (!Enum.IsDefined(typeof(MisfireHandlingMode), MisfireHandling)) { throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, "Misfire option '{0}' is not supported.", (int)MisfireHandling)); } } else { MisfireHandling = MisfireHandlingMode.Relaxed; } if (recurringJob.TryGetValue("V", out var version) && !String.IsNullOrWhiteSpace(version)) { Version = int.Parse(version, CultureInfo.InvariantCulture); } if (recurringJob.TryGetValue("RetryAttempt", out var attemptString) && int.TryParse(attemptString, out var retryAttempt)) { RetryAttempt = retryAttempt; } if (recurringJob.TryGetValue("Error", out var error) && !String.IsNullOrWhiteSpace(error)) { Error = error; } } public string RecurringJobId { get; } public string Queue { get; set; } public string Cron { get; set; } public string TimeZoneId { get; set; } public string Job { get; set; } public MisfireHandlingMode MisfireHandling { get; set; } public DateTime? CreatedAt { get; } public DateTime? NextExecution { get; private set; } public DateTime? LastExecution { get; set; } public string LastJobId { get; set; } public int? Version { get; private set; } public int RetryAttempt { get; set; } public string Error { get; set; } public void ScheduleNext(ITimeZoneResolver timeZoneResolver, DateTime from) { ScheduleNext(timeZoneResolver, from, from, TimeSpan.Zero); } public IEnumerable ScheduleNext( ITimeZoneResolver timeZoneResolver, DateTime from, DateTime now, TimeSpan precision) { if (timeZoneResolver == null) throw new ArgumentNullException(nameof(timeZoneResolver)); var result = new List(); var cron = ParseCronExpression(Cron); var timeZone = timeZoneResolver.GetTimeZoneById(TimeZoneId); DateTime? next = from; while ((next = cron.GetNextOccurrence(next.Value, timeZone, inclusive: false)) <= now) { if (next == now) { result.Add(next.Value); } else { switch (MisfireHandling) { case MisfireHandlingMode.Relaxed: next = now; result.Add(next.Value); break; case MisfireHandlingMode.Strict: result.Add(next.Value); break; case MisfireHandlingMode.Ignorable: if (now.Add(precision.Negate()) <= next && next <= now) { result.Add(next.Value); } break; } } } NextExecution = next; Error = null; return result; } public bool IsChanged(DateTime now, out IReadOnlyDictionary changedFields) { changedFields = GetChangedFields(now); return changedFields.Count > 0; } public void ScheduleRetry(DateTime nextAttempt, string error) { RetryAttempt++; Error = error; NextExecution = nextAttempt; } public void Disable(string error) { NextExecution = null; Error = error; } private IReadOnlyDictionary GetChangedFields(DateTime now) { var result = new Dictionary(); if ((_recurringJob.TryGetValue("Queue", out var queue) ? queue : null) != Queue) { result.Add("Queue", Queue); } if ((_recurringJob.TryGetValue("Cron", out var cron) ? cron : null) != Cron) { result.Add("Cron", Cron); } if ((_recurringJob.TryGetValue("TimeZoneId", out var timeZoneId) ? timeZoneId : null) != TimeZoneId) { result.Add("TimeZoneId", TimeZoneId); } if ((_recurringJob.TryGetValue("Job", out var job) ? job : null) != Job) { result.Add("Job", Job); } if (!_recurringJob.ContainsKey("CreatedAt")) { result.Add("CreatedAt", JobHelper.SerializeDateTime(now)); } var serializedLastExecution = LastExecution.HasValue ? JobHelper.SerializeDateTime(LastExecution.Value) : null; if ((_recurringJob.TryGetValue("LastExecution", out var lastExecution) ? lastExecution : null) != serializedLastExecution) { result.Add("LastExecution", serializedLastExecution ?? String.Empty); } var serializedNextExecution = NextExecution.HasValue ? JobHelper.SerializeDateTime(NextExecution.Value) : null; if ((_recurringJob.TryGetValue("NextExecution", out var next) ? next : null) != serializedNextExecution) { result.Add("NextExecution", serializedNextExecution ?? String.Empty); } if ((_recurringJob.TryGetValue("LastJobId", out var last) && !String.IsNullOrWhiteSpace(last) ? last : null) != LastJobId) { result.Add("LastJobId", LastJobId ?? String.Empty); } var misfireHandlingValue = MisfireHandling.ToString("D"); if ((!_recurringJob.ContainsKey("Misfire") && MisfireHandling != MisfireHandlingMode.Relaxed) || (_recurringJob.TryGetValue("Misfire", out var misfire) && misfire != misfireHandlingValue)) { result.Add("Misfire", misfireHandlingValue); } if (!_recurringJob.ContainsKey("V")) { result.Add("V", "2"); } if ((_recurringJob.TryGetValue("Error", out var error) && !String.IsNullOrWhiteSpace(error) ? error : null) != Error) { result.Add("Error", Error ?? String.Empty); } var retryAttemptValue = RetryAttempt.ToString(CultureInfo.InvariantCulture); if ((_recurringJob.TryGetValue("RetryAttempt", out var retryAttempt) ? retryAttempt : null) != retryAttemptValue) { if (_recurringJob.ContainsKey("RetryAttempt") || retryAttemptValue != "0") { result.Add("RetryAttempt", retryAttemptValue); } } return result; } public override string ToString() { return String.Join(";", _recurringJob.Select(static x => $"{x.Key}:{x.Value}")); } public static CronExpression ParseCronExpression([NotNull] string cronExpression) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); var format = CronFormat.Standard; if (!cronExpression.StartsWith("@", StringComparison.OrdinalIgnoreCase)) { var parts = cronExpression.Split(SeparatorCharacters, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 6) { format |= CronFormat.IncludeSeconds; } else if (parts.Length != 5) { throw new CronFormatException( $"Wrong number of parts in the `{cronExpression}` cron expression, you can only use 5 or 6 (with seconds) part-based expressions."); } } return CronExpression.Parse(cronExpression, format); } } } ================================================ FILE: src/Hangfire.Core/RecurringJobExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; namespace Hangfire { internal static class RecurringJobExtensions { public static IDisposable AcquireDistributedRecurringJobLock( [NotNull] this IStorageConnection connection, [NotNull] string recurringJobId, TimeSpan timeout) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); return connection.AcquireDistributedLock($"lock:recurring-job:{recurringJobId}", timeout); } public static RecurringJobEntity GetRecurringJob( [NotNull] this IStorageConnection connection, [NotNull] string recurringJobId) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); var recurringJob = connection.GetAllEntriesFromHash($"recurring-job:{recurringJobId}"); if (recurringJob == null || recurringJob.Count == 0) return null; return new RecurringJobEntity(recurringJobId, recurringJob); } public static RecurringJobEntity GetOrCreateRecurringJob( [NotNull] this IStorageConnection connection, [NotNull] string recurringJobId) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); var recurringJob = connection.GetAllEntriesFromHash($"recurring-job:{recurringJobId}"); if (recurringJob == null || recurringJob.Count == 0) recurringJob = new Dictionary(); return new RecurringJobEntity(recurringJobId, recurringJob); } public static void UpdateRecurringJob( [NotNull] this IWriteOnlyTransaction transaction, [NotNull] RecurringJobEntity recurringJob, [NotNull] IReadOnlyDictionary changedFields, [CanBeNull] ILog logger) { if (transaction == null) throw new ArgumentNullException(nameof(transaction)); if (recurringJob == null) throw new ArgumentNullException(nameof(recurringJob)); if (changedFields == null) throw new ArgumentNullException(nameof(changedFields)); if (changedFields.Count > 0) { transaction.SetRangeInHash($"recurring-job:{recurringJob.RecurringJobId}", changedFields); } var score = recurringJob.NextExecution.HasValue ? JobHelper.ToTimestamp(recurringJob.NextExecution.Value) : -1.0D; if (logger != null && logger.IsTraceEnabled()) { logger.Trace($"Recurring job '{recurringJob.RecurringJobId}' is being updated. RecurringJob: ({recurringJob}), Changes: ({String.Join(";", changedFields.Select(static x => $"{x.Key}:{x.Value}"))})"); } transaction.AddToSet( "recurring-jobs", recurringJob.RecurringJobId, score); } public static BackgroundJob TriggerRecurringJob( [NotNull] this IBackgroundJobFactory factory, [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] IProfiler profiler, [NotNull] RecurringJobEntity recurringJob, DateTime now) { if (factory == null) throw new ArgumentNullException(nameof(factory)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (connection == null) throw new ArgumentNullException(nameof(connection)); if (profiler == null) throw new ArgumentNullException(nameof(profiler)); if (recurringJob == null) throw new ArgumentNullException(nameof(recurringJob)); if (recurringJob.Job == null) { throw new InvalidOperationException("The 'Job' field has a null or empty value"); } var job = InvocationData.DeserializePayload(recurringJob.Job).DeserializeJob(); var context = new CreateContext(storage, connection, job, null, null, profiler, null); context.Parameters["RecurringJobId"] = recurringJob.RecurringJobId; context.Parameters["Time"] = JobHelper.ToTimestamp(now); var backgroundJob = factory.Create(context); recurringJob.LastExecution = now; recurringJob.LastJobId = backgroundJob?.Id; return backgroundJob; } public static void EnqueueBackgroundJob( [NotNull] this IStateMachine stateMachine, [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] IWriteOnlyTransaction transaction, [NotNull] RecurringJobEntity recurringJob, [NotNull] BackgroundJob backgroundJob, [CanBeNull] string reason, [NotNull] IProfiler profiler) { if (stateMachine == null) throw new ArgumentNullException(nameof(stateMachine)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (connection == null) throw new ArgumentNullException(nameof(connection)); if (transaction == null) throw new ArgumentNullException(nameof(transaction)); if (recurringJob == null) throw new ArgumentNullException(nameof(recurringJob)); if (backgroundJob == null) throw new ArgumentNullException(nameof(backgroundJob)); if (profiler == null) throw new ArgumentNullException(nameof(profiler)); var state = new EnqueuedState { Reason = reason }; if (recurringJob.Queue != null) { state.Queue = recurringJob.Queue; } stateMachine.ApplyState(new ApplyStateContext( storage, connection, transaction, backgroundJob, state, null, profiler, stateMachine)); } } } ================================================ FILE: src/Hangfire.Core/RecurringJobManager.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Profiling; using Hangfire.Storage; namespace Hangfire { /// /// Represents a recurring job manager that allows to create, update /// or delete recurring jobs. /// public class RecurringJobManager : IRecurringJobManager, IRecurringJobManagerV2 { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); private readonly ILog _logger = LogProvider.GetLogger(typeof(RecurringJobManager)); private readonly JobStorage _storage; private readonly IBackgroundJobFactory _factory; private readonly Func _nowFactory; private readonly ITimeZoneResolver _timeZoneResolver; public RecurringJobManager() : this(JobStorage.Current) { } public RecurringJobManager([NotNull] JobStorage storage) : this(storage, JobFilterProviders.Providers) { } public RecurringJobManager([NotNull] JobStorage storage, [NotNull] IJobFilterProvider filterProvider) : this(storage, filterProvider, new DefaultTimeZoneResolver()) { } public RecurringJobManager( [NotNull] JobStorage storage, [NotNull] IJobFilterProvider filterProvider, [NotNull] ITimeZoneResolver timeZoneResolver) : this(storage, filterProvider, timeZoneResolver, static () => DateTime.UtcNow) { } public RecurringJobManager( [NotNull] JobStorage storage, [NotNull] IJobFilterProvider filterProvider, [NotNull] ITimeZoneResolver timeZoneResolver, [NotNull] Func nowFactory) : this(storage, new BackgroundJobFactory(filterProvider), timeZoneResolver, nowFactory) { } public RecurringJobManager([NotNull] JobStorage storage, [NotNull] IBackgroundJobFactory factory) : this(storage, factory, new DefaultTimeZoneResolver()) { } public RecurringJobManager([NotNull] JobStorage storage, [NotNull] IBackgroundJobFactory factory, [NotNull] ITimeZoneResolver timeZoneResolver) : this(storage, factory, timeZoneResolver, static () => DateTime.UtcNow) { } internal RecurringJobManager( [NotNull] JobStorage storage, [NotNull] IBackgroundJobFactory factory, [NotNull] ITimeZoneResolver timeZoneResolver, [NotNull] Func nowFactory) { _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _factory = factory ?? throw new ArgumentNullException(nameof(factory)); _timeZoneResolver = timeZoneResolver ?? throw new ArgumentNullException(nameof(timeZoneResolver)); _nowFactory = nowFactory ?? throw new ArgumentNullException(nameof(nowFactory)); } public JobStorage Storage => _storage; public void AddOrUpdate(string recurringJobId, Job job, string cronExpression, RecurringJobOptions options) { if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); if (job == null) throw new ArgumentNullException(nameof(job)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); if (options == null) throw new ArgumentNullException(nameof(options)); ValidateCronExpression(cronExpression); if (job.Queue != null && !Storage.HasFeature(JobStorageFeatures.JobQueueProperty)) { throw new NotSupportedException("Current storage doesn't support specifying queues directly for a specific job. Please use the QueueAttribute instead."); } using (var connection = _storage.GetConnection()) using (connection.AcquireDistributedRecurringJobLock(recurringJobId, DefaultTimeout)) { var now = _nowFactory(); var recurringJob = connection.GetOrCreateRecurringJob(recurringJobId); var scheduleChanged = false; recurringJob.Job = InvocationData.SerializeJob(job).SerializePayload(); if (!cronExpression.Equals(recurringJob.Cron, StringComparison.OrdinalIgnoreCase)) { recurringJob.Cron = cronExpression; scheduleChanged = true; } if (!options.TimeZone.Id.Equals(recurringJob.TimeZoneId, StringComparison.OrdinalIgnoreCase)) { recurringJob.TimeZoneId = options.TimeZone.Id; scheduleChanged = true; } #pragma warning disable 618 recurringJob.Queue = options.QueueName; #pragma warning restore 618 recurringJob.MisfireHandling = options.MisfireHandling; recurringJob.RetryAttempt = 0; if (scheduleChanged || recurringJob.Error != null) { recurringJob.ScheduleNext(_timeZoneResolver, now.AddSeconds(-1)); } if (recurringJob.IsChanged(now, out var changedFields)) { using (var transaction = connection.CreateWriteTransaction()) { transaction.UpdateRecurringJob(recurringJob, changedFields, _logger); transaction.Commit(); } } } } private static void ValidateCronExpression(string cronExpression) { try { RecurringJobEntity.ParseCronExpression(cronExpression); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new ArgumentException( "CRON expression is invalid. Please see the inner exception for details.", nameof(cronExpression), ex); } } public void Trigger(string recurringJobId) => TriggerJob(recurringJobId); [Obsolete("Please use the `TriggerJob` method instead. Will be removed in 2.0.0.")] public string TriggerExecution(string recurringJobId) => TriggerJob(recurringJobId); public string TriggerJob(string recurringJobId) { if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); using (var connection = _storage.GetConnection()) using (connection.AcquireDistributedRecurringJobLock(recurringJobId, DefaultTimeout)) { var now = _nowFactory(); var recurringJob = connection.GetRecurringJob(recurringJobId); if (recurringJob == null) return null; BackgroundJob backgroundJob; try { backgroundJob = _factory.TriggerRecurringJob(_storage, connection, EmptyProfiler.Instance, recurringJob, now); recurringJob.ScheduleNext(_timeZoneResolver, now); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // TODO: Preserving backward compatibility, should be removed in 2.0.0. throw new AggregateException(ex); } if (recurringJob.IsChanged(now, out var changedFields)) { using (var transaction = connection.CreateWriteTransaction()) { if (backgroundJob != null) { _factory.StateMachine.EnqueueBackgroundJob( _storage, connection, transaction, recurringJob, backgroundJob, "Triggered using recurring job manager", EmptyProfiler.Instance); } transaction.UpdateRecurringJob(recurringJob, changedFields, _logger); transaction.Commit(); } return backgroundJob?.Id; } return null; } } public void RemoveIfExists(string recurringJobId) { if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); using (var connection = _storage.GetConnection()) using (connection.AcquireDistributedRecurringJobLock(recurringJobId, DefaultTimeout)) using (var transaction = connection.CreateWriteTransaction()) { transaction.RemoveHash($"recurring-job:{recurringJobId}"); transaction.RemoveFromSet("recurring-jobs", recurringJobId); transaction.Commit(); } } } } ================================================ FILE: src/Hangfire.Core/RecurringJobManagerExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Linq.Expressions; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.States; namespace Hangfire { public static class RecurringJobManagerExtensions { public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Job job, [NotNull] string cronExpression) { if (manager == null) throw new ArgumentNullException(nameof(manager)); manager.AddOrUpdate(recurringJobId, job, cronExpression, new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc }); } [Obsolete("Please use the AddOrUpdate(string, Job, string, RecurringJobOptions) method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Job job, [NotNull] string cronExpression, [NotNull] TimeZoneInfo timeZone) { AddOrUpdate(manager, recurringJobId, job, cronExpression, timeZone, EnqueuedState.DefaultQueue); } [Obsolete("Please use the AddOrUpdate(string, Job, string, RecurringJobOptions) method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Job job, [NotNull] string cronExpression, [NotNull] TimeZoneInfo timeZone, [NotNull] string queue) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (timeZone == null) throw new ArgumentNullException(nameof(timeZone)); if (queue == null) throw new ArgumentNullException(nameof(queue)); manager.AddOrUpdate( recurringJobId, job, cronExpression, new RecurringJobOptions { QueueName = queue, TimeZone = timeZone }); } [Obsolete("Please use the AddOrUpdate(string, Expression, Func, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression methodCall, [NotNull] Func cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use the AddOrUpdate(string, Expression>, Func, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use the AddOrUpdate(string, Expression, string, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression methodCall, [NotNull] string cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); if (methodCall == null) throw new ArgumentNullException(nameof(methodCall)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } [Obsolete("Please use the AddOrUpdate(string, Expression>, string, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); if (methodCall == null) throw new ArgumentNullException(nameof(methodCall)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } [Obsolete("Please use the AddOrUpdate(string, Expression>, Func, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use the AddOrUpdate(string, Expression>, Func, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), timeZone, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, methodCall, cronExpression(), options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] Func cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] Func cronExpression, [NotNull] RecurringJobOptions options) { if (queue == null) throw new ArgumentNullException(nameof(queue)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression(), options); } [Obsolete("Please use the AddOrUpdate(string, Expression>, string, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); if (methodCall == null) throw new ArgumentNullException(nameof(methodCall)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } [Obsolete("Please use the AddOrUpdate(string, Expression>, string, RecurringJobOptions) extension method instead. Will be removed in 2.0.0.")] public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression, TimeZoneInfo timeZone = null, string queue = EnqueuedState.DefaultQueue) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (recurringJobId == null) throw new ArgumentNullException(nameof(recurringJobId)); if (methodCall == null) throw new ArgumentNullException(nameof(methodCall)); if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, timeZone ?? TimeZoneInfo.Utc, queue); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); var job = Job.FromExpression(methodCall); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] string cronExpression) { AddOrUpdate(manager, recurringJobId, queue, methodCall, cronExpression, new RecurringJobOptions()); } public static void AddOrUpdate( [NotNull] this IRecurringJobManager manager, [NotNull] string recurringJobId, [NotNull] string queue, [NotNull] Expression> methodCall, [NotNull] string cronExpression, [NotNull] RecurringJobOptions options) { if (manager == null) throw new ArgumentNullException(nameof(manager)); if (queue == null) throw new ArgumentNullException(nameof(queue)); var job = Job.FromExpression(methodCall, queue); manager.AddOrUpdate(recurringJobId, job, cronExpression, options); } } } ================================================ FILE: src/Hangfire.Core/RecurringJobOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.States; namespace Hangfire { public class RecurringJobOptions { private TimeZoneInfo _timeZone; private string _queueName; public RecurringJobOptions() { TimeZone = TimeZoneInfo.Utc; #pragma warning disable 618 QueueName = EnqueuedState.DefaultQueue; #pragma warning restore 618 MisfireHandling = MisfireHandlingMode.Relaxed; } [NotNull] public TimeZoneInfo TimeZone { get { return _timeZone; } set { if (value == null) throw new ArgumentNullException(nameof(value)); _timeZone = value; } } [Obsolete("Please use non-obsolete AddOrUpdate with the explicit `queue` parameter instead. Will be removed in 2.0.0.")] [NotNull] public string QueueName { get { return _queueName; } set { EnqueuedState.ValidateQueueName(nameof(value), value); _queueName = value; } } public MisfireHandlingMode MisfireHandling { get; set; } } } ================================================ FILE: src/Hangfire.Core/Server/AspNetShutdownDetector.cs ================================================ // This file is part of Hangfire. Copyright © 2020 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using System.Threading; using Hangfire.Logging; namespace Hangfire.Server { internal static class AspNetShutdownDetector { private static readonly TimeSpan CheckForShutdownTimerInterval = TimeSpan.FromMilliseconds(250); [SuppressMessage("SonarLint", "S2930:IDisposablesShouldBeDisposed", Justification = "Has static lifetime, disposed on process shutdown.")] private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); #if !NETSTANDARD1_3 private static int _isInitialized; private static bool _isSucceeded; // ReSharper disable once NotAccessedField.Local private static Thread _checkForShutdownThread; private static Func _shutdownReasonFunc; private static Func _checkConfigChangedFunc; private static Func _disposingHttpRuntime; #endif public static bool IsSucceeded => #if !NETSTANDARD1_3 _isSucceeded #else false #endif ; public static CancellationToken GetShutdownToken() { #if !NETSTANDARD1_3 EnsureInitialized(); #endif return CancellationTokenSource.Token; } public static bool DisposingHttpRuntime => #if !NETSTANDARD1_3 _disposingHttpRuntime != null && _disposingHttpRuntime() #else false #endif ; #if !NETSTANDARD1_3 private static void EnsureInitialized() { if (Interlocked.Exchange(ref _isInitialized, 1) != 0) return; try { // Normally when ASP.NET is stopping our web application, IRegisteredObject.Stop // method is called for all the registered objects, and it is the recommended // way for handling shutdowns when we have some custom background threads. // // Hangfire uses OWIN's "host.OnAppDisposing" and "server.OnDispose" keys that // provide a cancellation token which is canceled after OWIN's own registered // object is stopped, and this method works most of the time. But... // Long-running web requests can prevent ASP.NET from stopping the registered // objects, because it waits for all the requests to end before shutting down // the application. But since .NET Framework 4.5.1, ASP.NET triggers the // StopListening event in these cases, so we can listen it and initiate a // shutdown once our application stopped to listen for the new requests. RegisterForStopListeningEvent(ref _isSucceeded); // Overlapped recycles feature is turned on by default in IIS, which cause // both old and new application to be active at the same time during app // domain recycles, which may cause old servers to process background jobs // from the new ones, resulting in exceptions when new methods added. // Also during deployments we can get numerous of startup/shutdown attempts, // because file updates aren't transactional, and since deploys often touch // multiple files. // Unfortunately during such deploys registered objects often don't stopped // carefully, especially when "autostart providers" feature is used, perhaps // due to some race conditions in ASP.NET. // After investigating source code of ASP.NET I've found that there's no better // solution for this issue other than to check the shutdown reason from time // to time. InitializeShutdownReason(ref _isSucceeded); // This check is based on the HttpRuntime._disposingHttpRuntime field that's // modified just before app domain is being unloaded, when new app domain was // already created. InitializeDisposingHttpRuntime(ref _isSucceeded); // And the last method to check for application shutdown is implemented in // SignalR and Kudu service by checking the UnsafeIISMethods.MgdHasConfigChanged // method. But I was failed to find this method in the recent ASP.NET sources. // But nevertheless it may be useful to have it for older versions. InitializeMgdHasConfigChanged(ref _isSucceeded); if (_isSucceeded) { _checkForShutdownThread = new Thread(CheckForAppDomainShutdown) { Name = "AspNetShutdownDetector", IsBackground = true, }; _checkForShutdownThread.Start(); } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { GetLogger().ErrorException("Failed to initialize shutdown triggers for ASP.NET application.", ex); } } private static void CheckForAppDomainShutdown(object state) { try { while (!CancellationTokenSource.IsCancellationRequested) { if (_checkConfigChangedFunc != null && _checkConfigChangedFunc()) { Cancel("UnsafeIISMethods.MgdHasConfigChanged"); break; } var shutdownReason = _shutdownReasonFunc?.Invoke(); if (shutdownReason != null) { Cancel($"HostingEnvironment.ShutdownReason: {shutdownReason}"); break; } if (_disposingHttpRuntime != null && _disposingHttpRuntime()) { Cancel("HttpRuntime._disposingHttpRuntime"); break; } Thread.Sleep(CheckForShutdownTimerInterval); } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { GetLogger().ErrorException( "An exception occurred while checking for ASP.NET shutdown, will not able to do the checks properly.", ex); } } private static void Cancel(string reason) { GetLogger().Info($"ASP.NET application is shutting down: {reason}."); try { CancellationTokenSource.Cancel(throwOnFirstException: false); } catch (ObjectDisposedException) { } catch (AggregateException ag) { GetLogger().ErrorException("One or more exceptions were thrown during app pool shutdown: ", ag); } } private static void RegisterForStopListeningEvent(ref bool success) { try { var hostingEnvironmentType = Type.GetType("System.Web.Hosting.HostingEnvironment, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false); if (hostingEnvironmentType == null) return; var stopEvent = hostingEnvironmentType.GetEvent("StopListening", BindingFlags.Static | BindingFlags.Public); if (stopEvent == null) return; stopEvent.AddEventHandler(null, new EventHandler(StopListening)); GetLogger().Debug("HostingEnvironment.StopListening shutdown trigger initialized successfully."); success = true; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { GetLogger().DebugException("Unable to initialize HostingEnvironment.StopListening shutdown trigger", ex); } } private static void StopListening(object sender, EventArgs e) { Cancel("HostingEnvironment.StopListening"); } private static void InitializeShutdownReason(ref bool success) { try { var hostingEnvironment = Type.GetType("System.Web.Hosting.HostingEnvironment, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false); if (hostingEnvironment == null) return; var shutdownReason = hostingEnvironment.GetProperty("ShutdownReason", BindingFlags.Static | BindingFlags.Public); if (shutdownReason == null) return; _shutdownReasonFunc = ShutdownReasonFunc; GetLogger().Debug("HostingEnvironment.ShutdownReason shutdown trigger initialized successfully."); success = true; string ShutdownReasonFunc() { try { var shutdownReasonValue = shutdownReason.GetValue(null); if (shutdownReasonValue != null && (int)shutdownReasonValue != 0) { return shutdownReasonValue.ToString(); } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { GetLogger().TraceException("Unable to call the HostingEnvironment.ShutdownReason property due to an exception.", ex); } return null; } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { GetLogger().DebugException("Unable to initialize HostingEnvironment.ShutdownReason shutdown trigger", ex); } } private static void InitializeMgdHasConfigChanged(ref bool success) { try { var type = Type.GetType("System.Web.Hosting.UnsafeIISMethods, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false); if (type == null) return; var methodInfo = type.GetMethod("MgdHasConfigChanged", BindingFlags.NonPublic | BindingFlags.Static); if (methodInfo == null) return; _checkConfigChangedFunc = (Func)Delegate.CreateDelegate(typeof(Func), methodInfo); GetLogger().Debug("UnsafeIISMethods.MgdHasConfigChanged shutdown trigger initialized successfully."); success = true; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { GetLogger().DebugException("Unable to initialize UnsafeIISMethods.MgdHasConfigChanged shutdown trigger", ex); } } private static void InitializeDisposingHttpRuntime(ref bool success) { try { var type = Type.GetType("System.Web.HttpRuntime, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false); if (type == null) return; var theRuntimeInfo = type.GetField("_theRuntime", BindingFlags.NonPublic | BindingFlags.Static); if (theRuntimeInfo == null) return; var disposingHttpRuntimeInfo = type.GetField("_disposingHttpRuntime", BindingFlags.NonPublic | BindingFlags.Instance); if (disposingHttpRuntimeInfo == null) return; var theRuntime = CreateGetStaticFieldDelegate(theRuntimeInfo); var disposingHttpRuntime = CreateGetFieldDelegate(disposingHttpRuntimeInfo, type); _disposingHttpRuntime = () => disposingHttpRuntime(theRuntime()); GetLogger().Debug("HttpRuntime._disposingHttpRuntime shutdown trigger initialized successfully."); success = true; } catch (Exception ex) { GetLogger().DebugException("Unable to initialize HttpRuntime._disposingHttpRuntime shutdown trigger", ex); } } private static Func CreateGetStaticFieldDelegate(FieldInfo fieldInfo) { var fieldExp = Expression.Field(null, fieldInfo); return Expression.Lambda>(fieldExp).Compile(); } private static Func CreateGetFieldDelegate(FieldInfo fieldInfo, Type type) { var instExp = Expression.Parameter(typeof(object)); var convExp = Expression.Convert(instExp, type); var fieldExp = Expression.Field(convExp, fieldInfo); return Expression.Lambda>(fieldExp, instExp).Compile(); } #endif private static ILog GetLogger() { return LogProvider.GetLogger(typeof(AspNetShutdownDetector)); } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundJobPerformer.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Profiling; namespace Hangfire.Server { public class BackgroundJobPerformer : IBackgroundJobPerformer { internal static readonly string ContextCanceledKey = "X_HF_Canceled"; private readonly IJobFilterProvider _filterProvider; private readonly IBackgroundJobPerformer _innerPerformer; public BackgroundJobPerformer() : this(JobFilterProviders.Providers) { } public BackgroundJobPerformer([NotNull] IJobFilterProvider filterProvider) : this(filterProvider, JobActivator.Current) { } public BackgroundJobPerformer( [NotNull] IJobFilterProvider filterProvider, [NotNull] JobActivator activator) : this(filterProvider, activator, TaskScheduler.Default) { } public BackgroundJobPerformer( [NotNull] IJobFilterProvider filterProvider, [NotNull] JobActivator activator, [CanBeNull] TaskScheduler taskScheduler) : this(filterProvider, new CoreBackgroundJobPerformer(activator, taskScheduler)) { } internal BackgroundJobPerformer( [NotNull] IJobFilterProvider filterProvider, [NotNull] IBackgroundJobPerformer innerPerformer) { if (filterProvider == null) throw new ArgumentNullException(nameof(filterProvider)); if (innerPerformer == null) throw new ArgumentNullException(nameof(innerPerformer)); _filterProvider = filterProvider; _innerPerformer = innerPerformer; } public object Perform(PerformContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); var filterInfo = GetFilters(context.BackgroundJob.Job); try { context.Performer = this; return PerformJobWithFilters(context, filterInfo.ServerFilters); } catch (JobAbortedException) { throw; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // TODO: Catch only JobPerformanceException, and pass InnerException to filters in 2.0.0. if (ex is OperationCanceledException && context.CancellationToken.ShutdownToken.IsCancellationRequested) { throw; } var exceptionContext = new ServerExceptionContext(context, ex); InvokeServerExceptionFilters(exceptionContext, filterInfo.ServerExceptionFiltersReversed); if (!exceptionContext.ExceptionHandled) { throw; } } finally { context.Performer = null; } return null; } private JobFilterInfo GetFilters(Job job) { return new JobFilterInfo(_filterProvider.GetFilters(job)); } private object PerformJobWithFilters(PerformContext context, JobFilterInfo.FilterCollection filters) { var preContext = new PerformingContext(context); var enumerator = filters.GetEnumerator(); return InvokeNextServerFilter(ref enumerator, _innerPerformer, context, preContext).Result; } private static PerformedContext InvokeNextServerFilter( ref JobFilterInfo.FilterCollection.Enumerator enumerator, IBackgroundJobPerformer innerPerformer, PerformContext context, PerformingContext preContext) { if (enumerator.MoveNext()) { return InvokeServerFilter(ref enumerator, innerPerformer, context, preContext); } var result = innerPerformer.Perform(context); return new PerformedContext(context, result, false, null); } private static PerformedContext InvokeServerFilter( ref JobFilterInfo.FilterCollection.Enumerator enumerator, IBackgroundJobPerformer innerPerformer, PerformContext context, PerformingContext preContext) { var filter = enumerator.Current!; preContext.Profiler.InvokeMeasured( new KeyValuePair(filter, preContext), InvokeOnPerforming, static ctx => $"OnPerforming for {ctx.Value.BackgroundJob.Id}" ); if (preContext.Canceled) { if (!preContext.Items.ContainsKey(ContextCanceledKey)) { preContext.Items.Add(ContextCanceledKey, filter.GetType().Name); } return new PerformedContext(preContext, null, true, null); } var wasError = false; PerformedContext postContext; try { postContext = InvokeNextServerFilter(ref enumerator, innerPerformer, context, preContext); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { wasError = true; postContext = new PerformedContext( preContext, null, false, ex); postContext.Profiler.InvokeMeasured( new KeyValuePair(filter, postContext), InvokeOnPerformed, static ctx => $"OnPerformed for {ctx.Value.BackgroundJob.Id}"); if (!postContext.ExceptionHandled) { throw; } } if (!wasError) { postContext.Profiler.InvokeMeasured( new KeyValuePair(filter, postContext), InvokeOnPerformed, static ctx => $"OnPerformed for {ctx.Value.BackgroundJob.Id}"); } return postContext; } private static void InvokeOnPerforming(KeyValuePair ctx) { try { ctx.Key.OnPerforming(ctx.Value); } catch (Exception filterException) when (filterException.IsCatchableExceptionType()) { CoreBackgroundJobPerformer.HandleJobPerformanceException( filterException, ctx.Value.CancellationToken, ctx.Value.BackgroundJob); } } private static void InvokeOnPerformed(KeyValuePair ctx) { try { ctx.Key.OnPerformed(ctx.Value); } catch (Exception filterException) when (filterException.IsCatchableExceptionType()) { CoreBackgroundJobPerformer.HandleJobPerformanceException( filterException, ctx.Value.CancellationToken, ctx.Value.BackgroundJob); } } private static void InvokeServerExceptionFilters( ServerExceptionContext context, JobFilterInfo.ReversedFilterCollection filters) { foreach (var filter in filters) { context.Profiler.InvokeMeasured( new KeyValuePair(filter, context), InvokeOnServerException, static ctx => $"OnServerException for {ctx.Value.BackgroundJob.Id}"); } } private static void InvokeOnServerException(KeyValuePair ctx) { try { ctx.Key.OnServerException(ctx.Value); } catch (Exception filterException) when (filterException.IsCatchableExceptionType()) { CoreBackgroundJobPerformer.HandleJobPerformanceException( filterException, ctx.Value.CancellationToken, ctx.Value.BackgroundJob); } } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundProcessContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; namespace Hangfire.Server { public class BackgroundProcessContext { [Obsolete("This constructor overload is deprecated and will be removed in 2.0.0.")] public BackgroundProcessContext( [NotNull] string serverId, [NotNull] JobStorage storage, [NotNull] IDictionary properties, CancellationToken cancellationToken) : this(serverId, storage, properties, Guid.NewGuid(), cancellationToken, cancellationToken, cancellationToken) { } public BackgroundProcessContext( [NotNull] string serverId, [NotNull] JobStorage storage, [NotNull] IDictionary properties, Guid executionId, CancellationToken stoppingToken, CancellationToken stoppedToken, CancellationToken shutdownToken) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (properties == null) throw new ArgumentNullException(nameof(properties)); ServerId = serverId; Storage = storage; ExecutionId = executionId; Properties = new Dictionary(properties, StringComparer.OrdinalIgnoreCase); StoppingToken = stoppingToken; StoppedToken = stoppedToken; ShutdownToken = shutdownToken; } [NotNull] public string ServerId { get; } [NotNull] public IReadOnlyDictionary Properties { get; } [NotNull] public JobStorage Storage { get; } public Guid ExecutionId { get; } [Obsolete("Please use the StoppingToken property instead, will be removed in 2.0.0.")] public CancellationToken CancellationToken => StoppingToken; public CancellationToken StoppingToken { get; } public CancellationToken StoppedToken { get; } public CancellationToken ShutdownToken { get; } public bool IsStopping => StoppingToken.IsCancellationRequested || StoppedToken.IsCancellationRequested || ShutdownToken.IsCancellationRequested; public bool IsStopped => StoppedToken.IsCancellationRequested || ShutdownToken.IsCancellationRequested; [Obsolete("Please use IsStopping or IsStopped properties instead. Will be removed in 2.0.0.")] public bool IsShutdownRequested => StoppingToken.IsCancellationRequested; public void Wait(TimeSpan timeout) { StoppingToken.WaitOrThrow(timeout); } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundProcessDispatcherBuilder.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Hangfire.Annotations; using Hangfire.Processing; namespace Hangfire.Server { internal sealed class BackgroundProcessDispatcherBuilder : IBackgroundProcessDispatcherBuilder { private readonly IBackgroundProcess _process; private readonly Func> _threadFactory; public BackgroundProcessDispatcherBuilder( [NotNull] IBackgroundProcess process, [NotNull] Func> threadFactory) { if (process == null) throw new ArgumentNullException(nameof(process)); if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory)); _process = process; _threadFactory = threadFactory; } public IBackgroundDispatcher Create(BackgroundServerContext context, BackgroundProcessingServerOptions options) { if (context == null) throw new ArgumentNullException(nameof(context)); if (options == null) throw new ArgumentNullException(nameof(options)); var execution = new BackgroundExecution( new BackgroundExecutionOptions { Name = _process.GetType().Name, RetryDelay = options.RetryDelay }, context.StoppingToken); return new BackgroundDispatcher( execution, ExecuteProcess, Tuple.Create(_process, context, execution), _threadFactory); } public override string ToString() { return _process.GetType().Name; } private static void ExecuteProcess(Guid executionId, object state) { var tuple = (Tuple)state; var serverContext = tuple.Item2; var context = new BackgroundProcessContext( serverContext.ServerId, serverContext.Storage, serverContext.Properties.ToDictionary(static x => x.Key, static x => x.Value), executionId, serverContext.StoppingToken, serverContext.StoppedToken, serverContext.ShutdownToken); while (!context.IsStopping) { tuple.Item1.Execute(context); tuple.Item3.NotifySucceeded(); } } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundProcessDispatcherBuilderAsync.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Linq; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Processing; namespace Hangfire.Server { internal sealed class BackgroundProcessDispatcherBuilderAsync : IBackgroundProcessDispatcherBuilder { private readonly int _maxConcurrency; private readonly bool _ownsScheduler; private readonly Func _taskScheduler; private readonly IBackgroundProcessAsync _process; public BackgroundProcessDispatcherBuilderAsync( [NotNull] IBackgroundProcessAsync process, [NotNull] Func taskScheduler, int maxConcurrency, bool ownsScheduler) { if (process == null) throw new ArgumentNullException(nameof(process)); if (taskScheduler == null) throw new ArgumentNullException(nameof(taskScheduler)); if (maxConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(maxConcurrency)); _process = process; _taskScheduler = taskScheduler; _maxConcurrency = maxConcurrency; _ownsScheduler = ownsScheduler; } public IBackgroundDispatcher Create(BackgroundServerContext context, BackgroundProcessingServerOptions options) { if (context == null) throw new ArgumentNullException(nameof(context)); if (options == null) throw new ArgumentNullException(nameof(options)); var execution = new BackgroundExecution( new BackgroundExecutionOptions { Name = _process.GetType().Name, RetryDelay = options.RetryDelay }, context.StoppingToken); return new BackgroundDispatcherAsync( execution, ExecuteProcess, Tuple.Create(_process, context, execution), _taskScheduler(), _maxConcurrency, _ownsScheduler); } public override string ToString() { return _process.GetType().Name; } private static async Task ExecuteProcess(Guid executionId, object state) { var tuple = (Tuple)state; var serverContext = tuple.Item2; var context = new BackgroundProcessContext( serverContext.ServerId, serverContext.Storage, serverContext.Properties.ToDictionary(static x => x.Key, static x => x.Value), executionId, serverContext.StoppingToken, serverContext.StoppedToken, serverContext.ShutdownToken); while (!context.IsStopping) { await tuple.Item1.ExecuteAsync(context).ConfigureAwait(true); tuple.Item3.NotifySucceeded(); } } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundProcessExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Logging; using Hangfire.Processing; namespace Hangfire.Server { public static class BackgroundProcessExtensions { public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcess process) { return UseBackgroundPool(process, Environment.ProcessorCount); } public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcess process, int threadCount) { return UseBackgroundPool(process, threadCount, null); } public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcess process, int threadCount, [CanBeNull] Action threadConfig) { if (process == null) throw new ArgumentNullException(nameof(process)); if (threadCount <= 0) throw new ArgumentOutOfRangeException(nameof(threadCount)); return UseBackgroundPool( process, (threadName, threadStart) => DefaultThreadFactory(threadCount, threadName, threadStart, threadConfig)); } public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcess process, [NotNull] Func> threadFactory) { if (process == null) throw new ArgumentNullException(nameof(process)); if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory)); return new BackgroundProcessDispatcherBuilder( process, threadStart => threadFactory(process.GetType().Name, threadStart)); } public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcessAsync process) { return UseBackgroundPool(process, Environment.ProcessorCount); } public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcessAsync process, int maxConcurrency) { return UseBackgroundPool( process, maxConcurrency, maxConcurrency < Environment.ProcessorCount ? maxConcurrency : Environment.ProcessorCount); } public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcessAsync process, int maxConcurrency, int threadCount) { if (process == null) throw new ArgumentNullException(nameof(process)); if (maxConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(maxConcurrency)); if (threadCount <= 0) throw new ArgumentOutOfRangeException(nameof(threadCount)); return UseBackgroundPool( process, maxConcurrency, (threadName, threadStart) => DefaultThreadFactory(threadCount, threadName, threadStart, null)); } public static IBackgroundProcessDispatcherBuilder UseBackgroundPool( [NotNull] this IBackgroundProcessAsync process, int maxConcurrency, [NotNull] Func> threadFactory) { if (process == null) throw new ArgumentNullException(nameof(process)); if (maxConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(maxConcurrency)); if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory)); Func createScheduler = () => new BackgroundTaskScheduler( threadStart => threadFactory(process.GetType().Name, threadStart), static exception => { LogProvider.GetLogger(typeof(BackgroundTaskScheduler)).FatalException( "Unhandled exception occurred in scheduler. Please report it to Hangfire developers", exception); }); return new BackgroundProcessDispatcherBuilderAsync(process, createScheduler, maxConcurrency, true); } public static IBackgroundProcessDispatcherBuilder UseThreadPool( [NotNull] this IBackgroundProcessAsync process) { return UseThreadPool(process, Environment.ProcessorCount); } public static IBackgroundProcessDispatcherBuilder UseThreadPool( [NotNull] this IBackgroundProcessAsync process, int maxConcurrency) { if (process == null) throw new ArgumentNullException(nameof(process)); if (maxConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(maxConcurrency)); return new BackgroundProcessDispatcherBuilderAsync(process, static () => TaskScheduler.Default, maxConcurrency, false); } internal static IEnumerable DefaultThreadFactory( int threadCount, [NotNull] string threadName, [NotNull] ThreadStart threadStart, [CanBeNull] Action threadConfig = null) { if (threadName == null) throw new ArgumentNullException(nameof(threadName)); if (threadStart == null) throw new ArgumentNullException(nameof(threadStart)); if (threadCount <= 0) throw new ArgumentOutOfRangeException(nameof(threadCount)); for (var i = 0; i < threadCount; i++) { var thread = new Thread(threadStart) { IsBackground = true, Name = $"{threadName} #{i + 1}" }; threadConfig?.Invoke(thread); yield return thread; } } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundProcessingServer.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Logging; using Hangfire.Processing; namespace Hangfire.Server { /// /// Responsible for running the given collection background processes. /// /// /// /// Immediately starts the processes in a background thread. /// Responsible for announcing/removing a server, bound to a storage. /// Wraps all the processes with an infinite loop and automatic retry. /// Executes all the processes in a single context. /// Uses timeout in dispose method, waits for all the components, cancel signals shutdown /// Contains some required processes and uses storage processes. /// Generates unique id. /// Properties are still bad. /// public sealed class BackgroundProcessingServer : IBackgroundProcessingServer { public static readonly TimeSpan DefaultShutdownTimeout = TimeSpan.FromSeconds(15); private static int _lastThreadId; private readonly ILog _logger = LogProvider.GetLogger(typeof(BackgroundProcessingServer)); private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); private readonly CancellationTokenSource _stoppedCts = new CancellationTokenSource(); private readonly CancellationTokenSource _shutdownCts = new CancellationTokenSource(); private CancellationTokenRegistration _shutdownRegistration; private readonly IBackgroundServerProcess _process; private readonly BackgroundProcessingServerOptions _options; private readonly IBackgroundDispatcher _dispatcher; private int _disposed; private bool _awaited; public BackgroundProcessingServer([NotNull] IEnumerable processes) : this(JobStorage.Current, processes) { } public BackgroundProcessingServer( [NotNull] IEnumerable processes, [NotNull] IDictionary properties) : this(JobStorage.Current, processes, properties) { } public BackgroundProcessingServer( [NotNull] JobStorage storage, [NotNull] IEnumerable processes) : this(storage, processes, new Dictionary()) { } public BackgroundProcessingServer( [NotNull] JobStorage storage, [NotNull] IEnumerable processes, [NotNull] IDictionary properties) : this(storage, processes, properties, new BackgroundProcessingServerOptions()) { } public BackgroundProcessingServer( [NotNull] JobStorage storage, [NotNull] IEnumerable processes, [NotNull] IDictionary properties, [NotNull] BackgroundProcessingServerOptions options) : this(storage, GetProcesses(processes), properties, options) { } public BackgroundProcessingServer( [NotNull] JobStorage storage, [NotNull] IEnumerable dispatcherBuilders, [NotNull] IDictionary properties, [NotNull] BackgroundProcessingServerOptions options) : this(new BackgroundServerProcess(storage, dispatcherBuilders, options, properties), options) { } /// /// Initializes a new instance of the /// class and immediately starts all the given background processes. /// internal BackgroundProcessingServer( [NotNull] BackgroundServerProcess process, [NotNull] BackgroundProcessingServerOptions options) { _process = process ?? throw new ArgumentNullException(nameof(process)); _options = options ?? throw new ArgumentNullException(nameof(options)); _dispatcher = CreateDispatcher(); #if !NETSTANDARD1_3 AppDomain.CurrentDomain.DomainUnload += OnCurrentDomainUnload; AppDomain.CurrentDomain.ProcessExit += OnCurrentDomainUnload; #endif _shutdownRegistration = AspNetShutdownDetector.GetShutdownToken().Register(OnAspNetShutdown); } public void SendStop() { ThrowIfDisposed(); _stoppingCts.Cancel(); _stoppedCts.CancelAfter(_options.StopTimeout); _shutdownCts.CancelAfter(_options.ShutdownTimeout); } public bool WaitForShutdown(TimeSpan timeout) { ThrowIfDisposed(); Volatile.Write(ref _awaited, true); return _dispatcher.Wait(timeout); } public async Task WaitForShutdownAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); Volatile.Write(ref _awaited, true); await _dispatcher.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false); } public void Dispose() { if (Volatile.Read(ref _disposed) == 1) return; _shutdownRegistration.Dispose(); if (!_stoppingCts.IsCancellationRequested) { SendStop(); } if (!Volatile.Read(ref _awaited)) { WaitForShutdown(Timeout.InfiniteTimeSpan); } if (Interlocked.Exchange(ref _disposed, 1) == 1) return; #if !NETSTANDARD1_3 AppDomain.CurrentDomain.DomainUnload -= OnCurrentDomainUnload; AppDomain.CurrentDomain.ProcessExit -= OnCurrentDomainUnload; #endif _dispatcher.Dispose(); _stoppingCts.Dispose(); _stoppedCts.Dispose(); _shutdownCts.Dispose(); } private void OnCurrentDomainUnload(object sender, EventArgs args) { if (Volatile.Read(ref _disposed) == 1) return; _logger.Warn("Stopping the server due to DomainUnload or ProcessExit event..."); _stoppingCts.Cancel(); _stoppedCts.Cancel(); _shutdownCts.Cancel(); if (!AspNetShutdownDetector.IsSucceeded) { // ASP.NET can be very sensitive to any delays during AppDomain unload. WaitForShutdown(_options.LastChanceTimeout); } } private void OnAspNetShutdown() { if (Volatile.Read(ref _disposed) == 1) { // Exit if our server was already disposed, there's no need to // throw ObjectDisposedException when unnecessary. return; } try { // When ASP.NET shutdown is detected, we only need to send a stop // signal to our background processing servers to allow correctly // await for background processing server shutdown during a direct // or indirect call to IRegisteredObject.Stop method, such as // OWIN's "onAppDisposing" event. SendStop(); } catch (ObjectDisposedException) { // There's a benign race condition, when SendStop is called after // processing server was already disposed. } } private static IBackgroundProcessDispatcherBuilder[] GetProcesses([NotNull] IEnumerable processes) { if (processes == null) throw new ArgumentNullException(nameof(processes)); return processes.Select(static x => x.UseBackgroundPool(threadCount: 1)).ToArray(); } private IBackgroundDispatcher CreateDispatcher() { var execution = new BackgroundExecution( new BackgroundExecutionOptions { Name = nameof(BackgroundServerProcess), ErrorThreshold = TimeSpan.Zero, StillErrorThreshold = TimeSpan.Zero, RetryDelay = retry => _options.RestartDelay }, _stoppingCts.Token); return new BackgroundDispatcher( execution, RunServer, execution, ThreadFactory); } private void RunServer(Guid executionId, object state) { _process.Execute(executionId, (BackgroundExecution)state, _stoppingCts.Token, _stoppedCts.Token, _shutdownCts.Token); } private static IEnumerable ThreadFactory(ThreadStart threadStart) { yield return new Thread(threadStart) { IsBackground = true, Name = $"{nameof(BackgroundServerProcess)} #{Interlocked.Increment(ref _lastThreadId)}", }; } private void ThrowIfDisposed() { if (Volatile.Read(ref _disposed) == 1) { throw new ObjectDisposedException(GetType().FullName); } } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundProcessingServerOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Processing; namespace Hangfire.Server { public sealed class BackgroundProcessingServerOptions { internal static TimeSpan DefaultStopTimeout = TimeSpan.FromMilliseconds(500); internal static TimeSpan DefaultLastChanceTimeout = TimeSpan.FromSeconds(1); internal static TimeSpan DefaultHeartbeatInterval = TimeSpan.FromSeconds(30); private Func _retryDelay; public BackgroundProcessingServerOptions() { HeartbeatInterval = DefaultHeartbeatInterval; ServerCheckInterval = ServerWatchdog.DefaultCheckInterval; ServerTimeout = ServerWatchdog.DefaultServerTimeout; CancellationCheckInterval = ServerJobCancellationWatcher.DefaultCheckInterval; RetryDelay = BackgroundExecutionOptions.GetBackOffMultiplier; RestartDelay = TimeSpan.FromSeconds(15); StopTimeout = DefaultStopTimeout; ShutdownTimeout = BackgroundProcessingServer.DefaultShutdownTimeout; LastChanceTimeout = DefaultLastChanceTimeout; } public TimeSpan HeartbeatInterval { get; set; } public TimeSpan ServerCheckInterval { get; set; } public TimeSpan ServerTimeout { get; set; } public TimeSpan CancellationCheckInterval { get; set; } public string ServerName { get; set; } public bool ExcludeStorageProcesses { get; set; } public Func RetryDelay { get => _retryDelay; set => _retryDelay = value ?? throw new ArgumentNullException(nameof(value)); } public TimeSpan StopTimeout { get; set; } public TimeSpan ShutdownTimeout { get; set; } public TimeSpan LastChanceTimeout { get; set; } public TimeSpan RestartDelay { get; set; } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundServerContext.cs ================================================ // This file is part of Hangfire. Copyright © 2018 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading; using Hangfire.Annotations; namespace Hangfire.Server { public class BackgroundServerContext { public BackgroundServerContext( [NotNull] string serverId, [NotNull] JobStorage storage, [NotNull] IDictionary properties, CancellationToken stoppingToken, CancellationToken stoppedToken, CancellationToken shutdownToken) { ServerId = serverId ?? throw new ArgumentNullException(nameof(serverId)); Storage = storage ?? throw new ArgumentNullException(nameof(storage)); Properties = properties ?? throw new ArgumentNullException(nameof(properties)); StoppingToken = stoppingToken; StoppedToken = stoppedToken; ShutdownToken = shutdownToken; } public string ServerId { get; } public JobStorage Storage { get; } public IDictionary Properties { get; } public CancellationToken StoppingToken { get; } public CancellationToken StoppedToken { get; } public CancellationToken ShutdownToken { get; } } } ================================================ FILE: src/Hangfire.Core/Server/BackgroundServerProcess.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Logging; using Hangfire.Processing; namespace Hangfire.Server { internal sealed class BackgroundServerProcess : IBackgroundServerProcess { private static readonly char[] ColonSeparator = new [] { ':' }; private readonly ILog _logger = LogProvider.GetLogger(typeof(BackgroundServerProcess)); private readonly JobStorage _storage; private readonly BackgroundProcessingServerOptions _options; private readonly IDictionary _properties; private readonly IBackgroundProcessDispatcherBuilder[] _dispatcherBuilders; public BackgroundServerProcess( [NotNull] JobStorage storage, [NotNull] IEnumerable dispatcherBuilders, [NotNull] BackgroundProcessingServerOptions options, [NotNull] IDictionary properties) { if (dispatcherBuilders == null) throw new ArgumentNullException(nameof(dispatcherBuilders)); _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _options = options ?? throw new ArgumentNullException(nameof(options)); _properties = properties ?? throw new ArgumentNullException(nameof(properties)); var builders = new List(); builders.AddRange(GetRequiredProcesses()); if (!options.ExcludeStorageProcesses) { builders.AddRange(GetStorageComponents()); } builders.AddRange(dispatcherBuilders); _dispatcherBuilders = builders.ToArray(); } public void Execute(Guid executionId, BackgroundExecution execution, CancellationToken stoppingToken, CancellationToken stoppedToken, CancellationToken shutdownToken) { var serverId = GetServerId(); Stopwatch stoppedAt = null; void HandleStopRestartSignal() => Interlocked.CompareExchange(ref stoppedAt, Stopwatch.StartNew(), null); void HandleStoppingSignal() => _logger.Info($"{GetServerTemplate(serverId)} caught stopping signal..."); void HandleStoppedSignal() => _logger.Info($"{GetServerTemplate(serverId)} caught stopped signal..."); void HandleShutdownSignal() => _logger.Warn($"{GetServerTemplate(serverId)} caught shutdown signal..."); void HandleRestartSignal() { if (!stoppingToken.IsCancellationRequested) { _logger.Info($"{GetServerTemplate(serverId)} caught restart signal..."); } } //using (LogProvider.OpenMappedContext("ServerId", serverId.ToString())) using (var restartCts = new CancellationTokenSource()) using (var restartStoppingCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, restartCts.Token)) using (var restartStoppedCts = CancellationTokenSource.CreateLinkedTokenSource(stoppedToken, restartCts.Token)) using (var restartShutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownToken, restartCts.Token)) using (restartStoppingCts.Token.Register(HandleStopRestartSignal)) using (stoppingToken.Register(HandleStoppingSignal)) using (stoppedToken.Register(HandleStoppedSignal)) using (shutdownToken.Register(HandleShutdownSignal)) using (restartCts.Token.Register(HandleRestartSignal)) { var context = new BackgroundServerContext( serverId, _storage, _properties, restartStoppingCts.Token, restartStoppedCts.Token, restartShutdownCts.Token); var dispatchers = new List(); CreateServer(context); try { // ReSharper disable once AccessToDisposedClosure using (var heartbeat = CreateHeartbeatProcess(context, () => restartCts.Cancel())) { StartDispatchers(context, dispatchers); execution.NotifySucceeded(); WaitForDispatchers(context, dispatchers); restartCts.Cancel(); // TODO Either modify the IBackgroundDispatcher.Wait method to handle CancellationToken // or expose the WaitHandle property to not to perform sync-over-async and vice versa // in 2.0. heartbeat.WaitAsync(Timeout.InfiniteTimeSpan, shutdownToken).GetAwaiter().GetResult(); } } finally { DisposeDispatchers(dispatchers); ServerDelete(context, stoppedAt); } } } private IBackgroundDispatcher CreateHeartbeatProcess(BackgroundServerContext context, Action requestRestart) { // We need to ensure that heartbeats are sent until server shutdown. Otherwise our server // can be considered as dead before completing all the background jobs on a graceful // shutdown. var heartbeatContext = new BackgroundServerContext( context.ServerId, context.Storage, context.Properties, context.ShutdownToken, context.ShutdownToken, context.ShutdownToken); return new ServerHeartbeatProcess(_options.HeartbeatInterval, _options.ServerTimeout, requestRestart) .UseBackgroundPool(threadCount: 1 #if !NETSTANDARD1_3 , static thread => { thread.Priority = ThreadPriority.AboveNormal; } #endif ) .Create(heartbeatContext, _options); } private IEnumerable GetRequiredProcesses() { yield return new ServerWatchdog(_options.ServerCheckInterval, _options.ServerTimeout).UseBackgroundPool(threadCount: 1); yield return new ServerJobCancellationWatcher(_options.CancellationCheckInterval).UseBackgroundPool(threadCount: 1); if (_storage.HasFeature(Storage.JobStorageFeatures.ProcessesInsteadOfComponents)) { foreach (var serverProcess in _storage.GetServerRequiredProcesses()) { yield return serverProcess.UseBackgroundPool(threadCount: 1); } } } private IEnumerable GetStorageComponents() { if (_storage.HasFeature(Storage.JobStorageFeatures.ProcessesInsteadOfComponents)) { return _storage.GetStorageWideProcesses().Select(static process => process.UseBackgroundPool(threadCount: 1)); } #pragma warning disable CS0618 // Type or member is obsolete return _storage.GetComponents().Select(static component => new ServerProcessDispatcherBuilder( #pragma warning restore CS0618 // Type or member is obsolete component, threadStart => BackgroundProcessExtensions.DefaultThreadFactory(1, component.GetType().Name, threadStart, null))); } private string GetServerId() { var serverName = _options.ServerName ?? Environment.GetEnvironmentVariable("COMPUTERNAME") ?? Environment.GetEnvironmentVariable("HOSTNAME") #if !NETSTANDARD1_3 ?? Environment.MachineName #endif ; var guid = Guid.NewGuid().ToString(); #if !NETSTANDARD1_3 if (!String.IsNullOrWhiteSpace(serverName)) { serverName += ":" + Process.GetCurrentProcess().Id; } #endif return !String.IsNullOrWhiteSpace(serverName) ? $"{serverName.ToLowerInvariant()}:{guid}" : guid; } private void CreateServer(BackgroundServerContext context) { _logger.Trace($"{GetServerTemplate(context.ServerId)} is announcing itself..."); var stopwatch = Stopwatch.StartNew(); using (var connection = _storage.GetConnection()) { connection.AnnounceServer(context.ServerId, GetServerContext(_properties)); } stopwatch.Stop(); ServerJobCancellationToken.AddServer(context.ServerId); _logger.Info($"{GetServerTemplate(context.ServerId)} successfully announced in {stopwatch.Elapsed.TotalMilliseconds} ms"); } private void ServerDelete(BackgroundServerContext context, Stopwatch stoppedAt) { try { _logger.Trace($"{GetServerTemplate(context.ServerId)} is reporting itself as stopped..."); ServerJobCancellationToken.RemoveServer(context.ServerId); var stopwatch = Stopwatch.StartNew(); using (var connection = _storage.GetConnection()) { connection.RemoveServer(context.ServerId); } stopwatch.Stop(); _logger.Info($"{GetServerTemplate(context.ServerId)} successfully reported itself as stopped in {stopwatch.Elapsed.TotalMilliseconds} ms"); _logger.Info($"{GetServerTemplate(context.ServerId)} has been stopped in total {stoppedAt?.Elapsed.TotalMilliseconds ?? 0} ms"); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.WarnException($"{GetServerTemplate(context.ServerId)} there was an exception, server may not be removed", ex); } } private void StartDispatchers(BackgroundServerContext context, ICollection dispatchers) { if (_dispatcherBuilders.Length == 0) { throw new InvalidOperationException("No dispatchers registered for the processing server."); } _logger.Info($"{GetServerTemplate(context.ServerId)} is starting the registered dispatchers: {String.Join(", ", _dispatcherBuilders.Select(static builder => $"{builder}"))}..."); foreach (var dispatcherBuilder in _dispatcherBuilders) { dispatchers.Add(dispatcherBuilder.Create(context, _options)); } _logger.Info($"{GetServerTemplate(context.ServerId)} all the dispatchers started"); } private void WaitForDispatchers( BackgroundServerContext context, IReadOnlyList dispatchers) { if (dispatchers.Count == 0) return; var waitTasks = new Task[dispatchers.Count]; for (var i = 0; i < dispatchers.Count; i++) { waitTasks[i] = dispatchers[i].WaitAsync(Timeout.InfiniteTimeSpan, CancellationToken.None); } var nonStopped = new List(); try { Task.WaitAll(waitTasks, context.ShutdownToken); } catch (OperationCanceledException) { for (var i = 0; i < dispatchers.Count; i++) { if (waitTasks[i].Status != TaskStatus.RanToCompletion) { nonStopped.Add(dispatchers[i]); } } } if (nonStopped.Count > 0) { var nonStoppedNames = nonStopped.Select(static dispatcher => $"{dispatcher}").ToArray(); _logger.Warn($"{GetServerTemplate(context.ServerId)} stopped non-gracefully due to {String.Join(", ", nonStoppedNames)}. Outstanding work on those dispatchers could be aborted, and there can be delays in background processing. This server instance will be incorrectly shown as active for a while. To avoid non-graceful shutdowns, investigate what prevents from stopping gracefully and add CancellationToken support for those methods."); } else { _logger.Info($"{GetServerTemplate(context.ServerId)} All dispatchers stopped"); } } private static void DisposeDispatchers(IEnumerable dispatchers) { foreach (var dispatcher in dispatchers) { dispatcher.Dispose(); } } private static ServerContext GetServerContext(IDictionary properties) { var serverContext = new ServerContext(); if (properties.TryGetValue("Queues", out var queues) && queues is string[] array) { serverContext.Queues = array; } if (properties.TryGetValue("WorkerCount", out var workerCount)) { serverContext.WorkerCount = (int)workerCount; } return serverContext; } internal static string GetServerTemplate(string serverId) { string name = serverId; try { var split = serverId.Split(ColonSeparator, StringSplitOptions.RemoveEmptyEntries); if (split.Length == 3 && split[2].Length > 8) { name = $"{split[0]}:{split[1]}:{split[2].Substring(0, 8)}"; } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // ignored } return $"Server {name}"; } } } ================================================ FILE: src/Hangfire.Core/Server/CoreBackgroundJobPerformer.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Processing; namespace Hangfire.Server { internal sealed class CoreBackgroundJobPerformer : IBackgroundJobPerformer { internal static readonly Dictionary> Substitutions = new Dictionary> { { typeof (IJobCancellationToken), static x => x.CancellationToken }, { typeof (CancellationToken), static x => x.CancellationToken.ShutdownToken }, { typeof (PerformContext), static x => x } }; private readonly JobActivator _activator; private readonly TaskScheduler _taskScheduler; public CoreBackgroundJobPerformer([NotNull] JobActivator activator, [CanBeNull] TaskScheduler taskScheduler) { _activator = activator ?? throw new ArgumentNullException(nameof(activator)); _taskScheduler = taskScheduler; } public object Perform(PerformContext context) { using (var scope = _activator.BeginScope(context)) { object instance = null; if (context.BackgroundJob.Job == null) { throw new InvalidOperationException("Can't perform a background job with a null job."); } if (!context.BackgroundJob.Job.Method.IsStatic) { instance = scope.Resolve(context.BackgroundJob.Job.Type); if (instance == null) { throw new InvalidOperationException( $"JobActivator returned NULL instance of the '{context.BackgroundJob.Job.Type}' type."); } } var arguments = SubstituteArguments(context); var result = InvokeMethod(context, instance, arguments); return result; } } internal static void HandleJobPerformanceException(Exception exception, IJobCancellationToken cancellationToken, [CanBeNull] BackgroundJob job) { if (exception is JobAbortedException) { // JobAbortedException exception should be thrown as-is to notify // a worker that background job was aborted by a state change, and // should NOT be re-queued. ExceptionDispatchInfo.Capture(exception).Throw(); } if (exception is OperationCanceledException && cancellationToken.IsAborted()) { // OperationCanceledException exception is thrown because // ServerJobCancellationWatcher has detected the job was aborted. throw new JobAbortedException(); } if (exception is OperationCanceledException && cancellationToken.ShutdownToken.IsCancellationRequested) { // OperationCanceledException exceptions are treated differently from // others, when ShutdownToken's cancellation was requested, to notify // a worker that job performance was aborted by a shutdown request, // and a job identifier should BE re-queued. ExceptionDispatchInfo.Capture(exception).Throw(); throw exception; } // Other exceptions are wrapped with JobPerformanceException to preserve a // shallow stack trace without Hangfire methods. throw new JobPerformanceException( "An exception occurred during performance of the job.", exception, job?.Id); } private object InvokeMethod(PerformContext context, object instance, object[] arguments) { if (context.BackgroundJob.Job == null) return null; try { var methodInfo = context.BackgroundJob.Job.Method; var method = new BackgroundJobMethod(methodInfo, instance, arguments); var returnType = methodInfo.ReturnType; if (returnType.IsTaskLike(out var getTaskFunc)) { if (_taskScheduler != null) { return InvokeOnTaskScheduler(context, method, getTaskFunc); } return InvokeOnTaskPump(context, method, getTaskFunc); } return InvokeSynchronously(method); } catch (ArgumentException ex) { HandleJobPerformanceException(ex, context.CancellationToken, context.BackgroundJob); throw; } catch (AggregateException ex) { HandleJobPerformanceException(ex.InnerException, context.CancellationToken, context.BackgroundJob); throw; } catch (TargetInvocationException ex) { HandleJobPerformanceException(ex.InnerException, context.CancellationToken, context.BackgroundJob); throw; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { HandleJobPerformanceException(ex, context.CancellationToken, context.BackgroundJob); throw; } } private object InvokeOnTaskScheduler(PerformContext context, BackgroundJobMethod method, Func getTaskFunc) { var scheduledTask = Task.Factory.StartNew( InvokeOnTaskSchedulerInternal, method, CancellationToken.None, TaskCreationOptions.None, _taskScheduler); var result = scheduledTask.GetAwaiter().GetResult(); if (result == null) return null; return getTaskFunc(result).GetTaskLikeResult(result, method.ReturnType); } private static object InvokeOnTaskSchedulerInternal(object state) { // ExecutionContext is captured automatically when calling the Task.Factory.StartNew // method, so we don't need to capture it manually. Please see the comment for // synchronous method execution below for details. return ((BackgroundJobMethod)state).Invoke(); } private static object InvokeOnTaskPump(PerformContext context, BackgroundJobMethod method, Func getTaskFunc) { // Using SynchronizationContext here is the best default option, where workers // are still running on synchronous dispatchers, and where a single job performer // may be used by multiple workers. We can't create a separate TaskScheduler // instance of every background job invocation, because TaskScheduler.Id may // overflow relatively fast, and can't use single scheduler for multiple performers // for better isolation in the default case – non-default external scheduler should // be used. It's also great to preserve backward compatibility for those who are // using Parallel.For(Each), since we aren't changing the TaskScheduler.Current. var oldSyncContext = SynchronizationContext.Current; try { using (var syncContext = new InlineSynchronizationContext()) using (var cancellationEvent = context.CancellationToken.ShutdownToken.GetCancellationEvent()) { SynchronizationContext.SetSynchronizationContext(syncContext); var result = InvokeSynchronously(method); if (result == null) return null; var task = getTaskFunc(result); var asyncResult = (IAsyncResult)task; var waitHandles = new[] { syncContext.WaitHandle, asyncResult.AsyncWaitHandle, cancellationEvent.WaitHandle }; while (!asyncResult.IsCompleted && WaitHandle.WaitAny(waitHandles) == 0) { var workItem = syncContext.Dequeue(); workItem.Item1(workItem.Item2); } return task.GetTaskLikeResult(result, method.ReturnType); } } finally { SynchronizationContext.SetSynchronizationContext(oldSyncContext); } } private static object InvokeSynchronously(object state) { var method = (BackgroundJobMethod) state; var executionContext = ExecutionContext.Capture(); if (executionContext != null) { // Asynchronous methods started with the TaskScheduler.StartNew method, capture the // current execution context by default and call the ExecutionContext.Run method on // a thread pool thread to pass the current AsyncLocal values there. // // As a side effect of this call, any updates to AsyncLocal values which happen inside // the background job method itself values aren't passed back to the calling thread // and end their lifetime as soon as method execution is finished. // // Synchronous methods don't need to capture the current execution context because the // thread is not changed. However, any updates to AsyncLocal values that happen inside // the background job method are passed back to the calling thread, expanding their // lifetime to the lifetime of the thread itself. This can result in memory leaks and // possibly affect future background job method executions in unexpected ways. // // To avoid this and to have the same behavior of AsyncLocal between synchronous and // asynchronous methods, we run synchronous ones in a captured execution context. The // ExecutionContext.Run method ensures that AsyncLocal values will be modified only in // the captured context and will not flow back to the parent context. ExecutionContext.Run(executionContext, InvokeSynchronouslyInternal, method); return method.Result; } return method.Invoke(); } private static void InvokeSynchronouslyInternal(object state) { ((BackgroundJobMethod)state).Invoke(); } private static object[] SubstituteArguments(PerformContext context) { if (context.BackgroundJob.Job == null) { return null; } var parameters = context.BackgroundJob.Job.Method.GetParameters(); var result = new List(context.BackgroundJob.Job.Args.Count); for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; var argument = context.BackgroundJob.Job.Args[i]; var value = Substitutions.TryGetValue(parameter.ParameterType, out var substitution) ? substitution(context) : argument; result.Add(value); } return result.ToArray(); } private sealed class BackgroundJobMethod(MethodInfo methodInfo, object instance, object[] parameters) { public Type ReturnType => methodInfo.ReturnType; public object Result { get; private set; } public object Invoke() { Result = methodInfo.Invoke(instance, parameters); return Result; } } } } ================================================ FILE: src/Hangfire.Core/Server/DelayedJobScheduler.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; namespace Hangfire.Server { /// /// Represents a background process responsible for enqueueing delayed /// jobs. /// /// /// /// This background process polls the delayed job schedule for /// delayed jobs that are ready to be enqueued. To prevent a stress load /// on a job storage, the configurable delay is used between scheduler /// runs. Delay is used only when there are no more background jobs to be /// enqueued. /// /// When a background job is ready to be enqueued, it is simply /// moved from to the /// by using . /// /// Delayed job schedule is based on a Set data structure of a job /// storage, so you can use this background process as an example of a /// custom extension. /// /// Multiple instances of this background process can be used in /// separate threads/processes without additional configuration (distributed /// locks are used). However, this only adds support for fail-over, and does /// not increase the performance. /// /// /// If you are using custom filter providers, you need to pass a custom /// instance to make this process /// respect your filters when enqueueing background jobs. /// /// /// /// /// /// public class DelayedJobScheduler : IBackgroundProcess { /// /// Represents a default polling interval for delayed job scheduler. /// This field is read-only. /// /// /// The value of this field is TimeSpan.FromSeconds(15). /// public static readonly TimeSpan DefaultPollingDelay = TimeSpan.FromSeconds(15); private static readonly TimeSpan DefaultLockTimeout = TimeSpan.FromMinutes(1); private static readonly int BatchSize = 1000; private static readonly int MaxStateChangeAttempts = 5; private readonly ILog _logger = LogProvider.For(); private readonly ConcurrentDictionary _isBatchingAvailableCache = new ConcurrentDictionary(); private readonly IBackgroundJobStateChanger _stateChanger; private readonly IProfiler _profiler; private readonly TimeSpan _pollingDelay; private bool _parallelismIssueLogged; /// /// Initializes a new instance of the /// class with the value as a /// delay between runs. /// public DelayedJobScheduler() : this(DefaultPollingDelay) { } /// /// Initializes a new instance of the /// class with a specified polling interval. /// /// Delay between scheduler runs. public DelayedJobScheduler(TimeSpan pollingDelay) : this(pollingDelay, new BackgroundJobStateChanger()) { } /// /// Initializes a new instance of the /// class with a specified polling interval and given state changer. /// /// Delay between scheduler runs. /// State changer to use for background jobs. /// /// is null. public DelayedJobScheduler(TimeSpan pollingDelay, [NotNull] IBackgroundJobStateChanger stateChanger) { if (stateChanger == null) throw new ArgumentNullException(nameof(stateChanger)); _stateChanger = stateChanger; _pollingDelay = pollingDelay; _profiler = new SlowLogProfiler(_logger); } /// /// Gets or sets the maximum degree of parallelism for a scheduler instance. /// When greater than 1 and batching enabling, delayed jobs will /// be scheduled in parallel under separate connections, increasing the /// throughput. /// public int MaxDegreeOfParallelism { get; set; } /// /// Gets or sets a task scheduler that will be used when parallel scheduling /// is enabled via the option. /// public TaskScheduler TaskScheduler { get; set; } internal Func RetryDelayFunc { get; set; } = attempt => TimeSpan.FromSeconds(attempt); /// public void Execute(BackgroundProcessContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); int jobsProcessed; do { jobsProcessed = EnqueueNextScheduledJobs(context); if (jobsProcessed != 0) { _logger.Debug($"{jobsProcessed} scheduled job(s) processed by scheduler."); } } while (jobsProcessed > 0 && !context.IsStopping); context.Wait(_pollingDelay); } /// public override string ToString() { return GetType().Name; } private int EnqueueNextScheduledJobs(BackgroundProcessContext context) { return UseConnectionDistributedLock(context.Storage, connection => { var jobsProcessed = 0; var now = !context.Storage.HasFeature(JobStorageFeatures.Connection.GetUtcDateTime) ? DateTime.UtcNow : ((JobStorageConnection)connection).GetUtcDateTime(); if (IsBatchingAvailable(context.Storage, connection)) { var timestamp = JobHelper.ToTimestamp(now); var entries = ((JobStorageConnection)connection).GetFirstByLowestScoreFromSet("schedule", 0, timestamp, BatchSize); var toBeTransactionallyEnqueued = new List>(); var toBeSequentiallyEnqueued = new List(); if (entries != null) { foreach (var entry in entries) { if (context.IsStopping) break; var colonIndex = entry.IndexOf(':'); if (colonIndex < 0) toBeSequentiallyEnqueued.Add(entry); else toBeTransactionallyEnqueued.Add(Tuple.Create(entry, colonIndex)); jobsProcessed++; } #if !NETSTANDARD1_3 if (MaxDegreeOfParallelism > 1) { Parallel.ForEach( toBeSequentiallyEnqueued, new ParallelOptions { MaxDegreeOfParallelism = MaxDegreeOfParallelism, CancellationToken = context.StoppingToken, TaskScheduler = TaskScheduler }, (jobId, state) => { using (var dedicated = context.Storage.GetConnection()) { EnqueueBackgroundJob(context, dedicated, jobId); } }); } else #endif { foreach (var jobId in toBeSequentiallyEnqueued) { EnqueueBackgroundJob(context, connection, jobId); } } if (toBeTransactionallyEnqueued.Count > 0) { using (var transaction = connection.CreateWriteTransaction()) { foreach (var tuple in toBeTransactionallyEnqueued) { EnqueueEntry(tuple.Item1, tuple.Item2, transaction); } transaction.Commit(); } } } } else { if (MaxDegreeOfParallelism > 1 && !_parallelismIssueLogged) { _logger.Warn("Parallel execution is configured but can't be used, because current storage implementation doesn't support batching."); _parallelismIssueLogged = true; } for (var i = 0; i < BatchSize; i++) { if (context.IsStopping) break; var timestamp = JobHelper.ToTimestamp(now); var entry = connection.GetFirstByLowestScoreFromSet("schedule", 0, timestamp); if (entry == null) break; var colonIndex = entry.IndexOf(':'); if (colonIndex < 0) { EnqueueBackgroundJob(context, connection, entry); } else { using (var transaction = connection.CreateWriteTransaction()) { EnqueueEntry(entry, colonIndex, transaction); transaction.Commit(); } } jobsProcessed++; } } return jobsProcessed; }); } private static void EnqueueEntry(string entry, int colonIndex, IWriteOnlyTransaction transaction) { if (colonIndex < 0) throw new ArgumentOutOfRangeException(nameof(colonIndex)); var queue = entry.Substring(0, colonIndex); var jobId = entry.Substring(colonIndex + 1); transaction.RemoveFromSet("schedule", entry); transaction.AddToQueue(queue, jobId); } private void EnqueueBackgroundJob(BackgroundProcessContext context, IStorageConnection connection, string jobId) { Exception exception = null; // At least one retry attempt should always be performed. var maxRetryAttempts = MaxStateChangeAttempts > 0 ? MaxStateChangeAttempts : 1; for (var retryAttempt = 0; retryAttempt < maxRetryAttempts; retryAttempt++) { try { var appliedState = _stateChanger.ChangeState(new StateChangeContext( context.Storage, connection, jobId, new EnqueuedState { Reason = $"Triggered by {ToString()}" }, new [] { ScheduledState.StateName }, disableFilters: false, context.StoppingToken, _profiler, context.ServerId)); if (appliedState == null) { _logger.Debug($"Failed to change state of a scheduled background job '{jobId}'"); // When a background job with the given id does not exist, or its state // does not equal to the Scheduled one, we should remove its id manually // to avoid poisoned schedule and be able to process other scheduled jobs. // This might happen when someone modifies the storage bypassing Hangfire API. using (connection.AcquireDistributedJobLock(jobId, TimeSpan.FromSeconds(5))) { var jobData = connection.GetJobData(jobId); if (jobData == null || !ScheduledState.StateName.Equals(jobData.State, StringComparison.OrdinalIgnoreCase)) { using (var transaction = connection.CreateWriteTransaction()) { transaction.RemoveFromSet("schedule", jobId); transaction.Commit(); } _logger.Warn($"Background job '{jobId}' removed from the schedule, because it's expired or its state was changed"); } } } return; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.DebugException( $"State change attempt {retryAttempt + 1} of {MaxStateChangeAttempts} failed due to an error, see inner exception for details", ex); exception = ex; } context.Wait(RetryDelayFunc(retryAttempt)); } _logger.ErrorException( $"{MaxStateChangeAttempts} state change attempt(s) failed due to an exception, moving job to the FailedState", exception); // When exception occurs, it's essential to remove a background job identifier from the schedule, // because otherwise delayed job scheduler will fetch such a failing job identifier again and again // and will be unable to make any progress. Any successful state change will cause that identifier // to be removed from the schedule. _stateChanger.ChangeState(new StateChangeContext( context.Storage, connection, jobId, new FailedState(exception, context.ServerId) { Reason = $"Failed to change state to the '{EnqueuedState.StateName}' one due to an exception after {MaxStateChangeAttempts} retry attempts" }, new[] { ScheduledState.StateName }, disableFilters: true, context.StoppingToken, _profiler, context.ServerId)); } // TODO Use new HasFeature method if available to avoid exceptions private bool IsBatchingAvailable(JobStorage storage, IStorageConnection connection) { if (storage.HasFeature(JobStorageFeatures.Connection.BatchedGetFirstByLowest) || storage.HasFeature("BatchedGetFirstByLowestScoreFromSet")) // FROM RCs { return true; } return _isBatchingAvailableCache.GetOrAdd( connection.GetType(), type => { if (connection is JobStorageConnection storageConnection) { try { storageConnection.GetFirstByLowestScoreFromSet(null, 0, 0, 1); } catch (ArgumentNullException ex) when (ex.ParamName == "key") { return true; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // } } return false; }); } private T UseConnectionDistributedLock(JobStorage storage, Func action) { var resource = "locks:schedulepoller"; try { using (var connection = storage.GetConnection()) using (connection.AcquireDistributedLock(resource, DefaultLockTimeout)) { return action(connection); } } catch (DistributedLockTimeoutException e) when (e.Resource.EndsWith(resource, StringComparison.Ordinal)) { // DistributedLockTimeoutException here doesn't mean that delayed jobs weren't enqueued. // It just means another Hangfire server did this work. _logger.DebugException( $@"An exception was thrown during acquiring distributed lock on the {resource} resource within {DefaultLockTimeout.TotalSeconds} seconds. The scheduled jobs have not been handled this time. It will be retried in {_pollingDelay.TotalSeconds} seconds", e); return default(T); } } } } ================================================ FILE: src/Hangfire.Core/Server/IBackgroundJobPerformer.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Server { public interface IBackgroundJobPerformer { object Perform(PerformContext context); } } ================================================ FILE: src/Hangfire.Core/Server/IBackgroundProcess.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; #pragma warning disable 618 // Obsolete member namespace Hangfire.Server { /// /// Provides methods for defining processes that will be executed in a /// background thread by . /// /// /// /// Needs a wait. /// Cancellation token /// Connection disposal /// /// /// public interface IBackgroundProcess : IServerProcess { /// /// /// /// Context for a background process. /// is null. void Execute([NotNull] BackgroundProcessContext context); } } ================================================ FILE: src/Hangfire.Core/Server/IBackgroundProcessAsync.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Threading.Tasks; using Hangfire.Annotations; namespace Hangfire.Server { public interface IBackgroundProcessAsync { Task ExecuteAsync([NotNull] BackgroundProcessContext context); } } ================================================ FILE: src/Hangfire.Core/Server/IBackgroundProcessDispatcherBuilder.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Annotations; using Hangfire.Processing; namespace Hangfire.Server { public interface IBackgroundProcessDispatcherBuilder { IBackgroundDispatcher Create([NotNull] BackgroundServerContext context, [NotNull] BackgroundProcessingServerOptions options); } } ================================================ FILE: src/Hangfire.Core/Server/IBackgroundProcessingServer.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; using System.Threading.Tasks; namespace Hangfire.Server { public interface IBackgroundProcessingServer : IDisposable { void SendStop(); bool WaitForShutdown(TimeSpan timeout); Task WaitForShutdownAsync(CancellationToken cancellationToken); } } ================================================ FILE: src/Hangfire.Core/Server/IBackgroundServerProcess.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; using Hangfire.Processing; namespace Hangfire.Server { internal interface IBackgroundServerProcess { void Execute( Guid executionId, BackgroundExecution execution, CancellationToken stoppingToken, CancellationToken stoppedToken, CancellationToken shutdownToken); } } ================================================ FILE: src/Hangfire.Core/Server/IServerExceptionFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Server { /// /// Defines methods that are required for the server exception filter. /// public interface IServerExceptionFilter { /// /// Called when an exception occurred during the performance of the job. /// /// The filter context. void OnServerException(ServerExceptionContext filterContext); } } ================================================ FILE: src/Hangfire.Core/Server/IServerFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Server { /// /// Defines methods that are required for a server filter. /// public interface IServerFilter { /// /// Called before the performance of the job. /// /// The filter context. void OnPerforming(PerformingContext context); /// /// Called after the performance of the job. /// /// The filter context. void OnPerformed(PerformedContext context); } } ================================================ FILE: src/Hangfire.Core/Server/JobAbortedException.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; namespace Hangfire.Server { #if !NETSTANDARD1_3 [Serializable] #endif public class JobAbortedException : OperationCanceledException { public JobAbortedException() { } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected JobAbortedException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif } } ================================================ FILE: src/Hangfire.Core/Server/JobPerformanceException.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; namespace Hangfire.Server { #if !NETSTANDARD1_3 [Serializable] #endif public class JobPerformanceException : Exception { public JobPerformanceException(string message, Exception innerException) : this(message, innerException, null) { } public JobPerformanceException(string message, Exception innerException, string jobId) : base(message, innerException) { JobId = jobId; } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected JobPerformanceException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif /// /// The Background Job Id of the Job instance this exception has been raised for /// public string JobId { get; private set; } } } ================================================ FILE: src/Hangfire.Core/Server/PerformContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Profiling; using Hangfire.Storage; namespace Hangfire.Server { /// /// Provides information about the context in which the job /// is performed. /// public class PerformContext { public PerformContext([NotNull] PerformContext context) : this(context.Storage, context.Connection, context.BackgroundJob, context.CancellationToken, context.Profiler, context.ServerId, context.Items) { Performer = context.Performer; } [Obsolete("Please use PerformContext(JobStorage, IStorageConnection, BackgroundJob, IJobCancellationToken) overload instead. Will be removed in 2.0.0.")] public PerformContext( [NotNull] IStorageConnection connection, [NotNull] BackgroundJob backgroundJob, [NotNull] IJobCancellationToken cancellationToken) : this(null, connection, backgroundJob, cancellationToken) { } public PerformContext( [CanBeNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] BackgroundJob backgroundJob, [NotNull] IJobCancellationToken cancellationToken) : this(storage, connection, backgroundJob, cancellationToken, EmptyProfiler.Instance, null, null) { } internal PerformContext( [CanBeNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] BackgroundJob backgroundJob, [NotNull] IJobCancellationToken cancellationToken, [NotNull] IProfiler profiler, [CanBeNull] string serverId, [CanBeNull] IDictionary items) { Storage = storage; Connection = connection ?? throw new ArgumentNullException(nameof(connection)); BackgroundJob = backgroundJob ?? throw new ArgumentNullException(nameof(backgroundJob)); CancellationToken = cancellationToken ?? throw new ArgumentNullException(nameof(cancellationToken)); Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); ServerId = serverId; Items = items ?? new Dictionary(); } [CanBeNull] public JobStorage Storage { get; } /// /// Gets an instance of the key-value storage. You can use it /// to pass additional information between different client filters /// or just between different methods. /// [NotNull] public IDictionary Items { get; } [NotNull] public BackgroundJob BackgroundJob { get; } [Obsolete("Please use BackgroundJob property instead. Will be removed in 2.0.0.")] public string JobId => BackgroundJob.Id; [Obsolete("Please use BackgroundJob property instead. Will be removed in 2.0.0.")] public Job Job => BackgroundJob.Job; [Obsolete("Please use BackgroundJob property instead. Will be removed in 2.0.0.")] public DateTime CreatedAt => BackgroundJob.CreatedAt; [NotNull] public IJobCancellationToken CancellationToken { get; } [NotNull] public IStorageConnection Connection { get; } [NotNull] internal IProfiler Profiler { get; } [CanBeNull] public IBackgroundJobPerformer Performer { get; internal set; } [CanBeNull] public string ServerId { get; } public void SetJobParameter([NotNull] string name, object value) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); Connection.SetJobParameter(BackgroundJob.Id, name, SerializationHelper.Serialize(value, SerializationOption.User)); } public T GetJobParameter([NotNull] string name) => GetJobParameter(name, allowStale: false); public T GetJobParameter([NotNull] string name, bool allowStale) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); try { string value; if (allowStale && BackgroundJob.ParametersSnapshot != null) { BackgroundJob.ParametersSnapshot.TryGetValue(name, out value); } else { value = Connection.GetJobParameter(BackgroundJob.Id, name); } return SerializationHelper.Deserialize(value, SerializationOption.User); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new InvalidOperationException( $"Could not get a value of the job parameter `{name}`. See inner exception for details.", ex); } } } } ================================================ FILE: src/Hangfire.Core/Server/PerformedContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; namespace Hangfire.Server { /// /// Provides the context for the /// method of the interface. /// public class PerformedContext : PerformContext { public PerformedContext( [NotNull] PerformContext context, [CanBeNull]object result, bool canceled, [CanBeNull] Exception exception) : base(context) { Result = result; Canceled = canceled; Exception = exception; } /// /// Gets a value that was returned by the job. /// [CanBeNull] public object Result { get; } /// /// Gets a value that indicates that this /// object was canceled. /// public bool Canceled { get; } /// /// Gets an exception that occurred during the performance of the job. /// [CanBeNull] public Exception Exception { get; } /// /// Gets or sets a value that indicates that this /// object handles an exception occurred during the performance of the job. /// public bool ExceptionHandled { get; set; } } } ================================================ FILE: src/Hangfire.Core/Server/PerformingContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Server { /// /// Provides the context for the /// method of the interface. /// public class PerformingContext : PerformContext { public PerformingContext(PerformContext context) : base(context) { } /// /// Gets or sets a value that indicates that this /// object was canceled. /// public bool Canceled { get; set; } } } ================================================ FILE: src/Hangfire.Core/Server/RecurringJobScheduler.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Profiling; using Hangfire.Storage; namespace Hangfire.Server { /// /// Represents a background process responsible for enqueueing recurring /// jobs. /// /// /// /// This background process polls the recurring job schedule /// for recurring jobs ready to be enqueued. Interval between scheduler /// polls is hard-coded to 1 minute as a compromise between /// frequency and additional stress on job storage. /// /// /// Use custom background processes if you need to schedule recurring jobs /// with frequency less than one minute. Please see the /// interface for details. /// /// /// Recurring job schedule is based on Set and Hash data structures /// of a job storage, so you can use this background process as an example /// of a custom extension. /// /// Multiple instances of this background process can be used in /// separate threads/processes without additional configuration (distributed /// locks are used). However, this only adds support for fail-over, and does /// not increase the performance. /// /// /// If you are using custom filter providers, you need to pass a /// custom instance to make this /// process respect your filters when enqueueing background jobs. /// /// /// /// /// /// public class RecurringJobScheduler : IBackgroundProcess { private static readonly TimeSpan LockTimeout = TimeSpan.FromMinutes(1); private static readonly int BatchSize = 1000; private static readonly int MaxRetryAttemptCount = 5; private static readonly int MaxSupportedVersion = 2; private readonly ILog _logger = LogProvider.For(); private readonly ConcurrentDictionary _isBatchingAvailableCache = new ConcurrentDictionary(); private readonly IBackgroundJobFactory _factory; private readonly Func _nowFactory; private readonly ITimeZoneResolver _timeZoneResolver; private readonly TimeSpan _pollingDelay; private readonly IProfiler _profiler; private bool _parallelismIssueLogged; /// /// Initializes a new instance of the /// class with default background job factory. /// public RecurringJobScheduler() : this(new BackgroundJobFactory(JobFilterProviders.Providers)) { } /// /// Initializes a new instance of the /// class with custom background job factory and a state machine. /// /// Factory that will be used to create background jobs. /// /// is null. public RecurringJobScheduler( [NotNull] IBackgroundJobFactory factory) : this(factory, TimeSpan.Zero) { } /// /// Initializes a new instance of the class /// with custom background job factory, state machine and clocks. /// /// Factory that will be used to create background jobs. /// Delay before another polling attempt, when no jobs scheduled yet. /// is null. public RecurringJobScheduler( [NotNull] IBackgroundJobFactory factory, TimeSpan pollingDelay) : this(factory, pollingDelay, new DefaultTimeZoneResolver()) { } /// /// Initializes a new instance of the class /// with custom background job factory, state machine and clocks. /// /// Factory that will be used to create background jobs. /// Delay before another polling attempt, when no jobs scheduled yet. /// Function that returns a time zone object by its identifier. /// is null. /// is null. public RecurringJobScheduler( [NotNull] IBackgroundJobFactory factory, TimeSpan pollingDelay, [NotNull] ITimeZoneResolver timeZoneResolver) : this(factory, pollingDelay, timeZoneResolver, static () => DateTime.UtcNow) { } public RecurringJobScheduler( [NotNull] IBackgroundJobFactory factory, TimeSpan pollingDelay, [NotNull] ITimeZoneResolver timeZoneResolver, [NotNull] Func nowFactory) { if (factory == null) throw new ArgumentNullException(nameof(factory)); if (nowFactory == null) throw new ArgumentNullException(nameof(nowFactory)); if (timeZoneResolver == null) throw new ArgumentNullException(nameof(timeZoneResolver)); _factory = factory; _nowFactory = nowFactory; _timeZoneResolver = timeZoneResolver; _pollingDelay = pollingDelay; _profiler = new SlowLogProfiler(_logger); } /// /// Gets or sets the maximum degree of parallelism for a scheduler instance. /// When greater than 1 and batching enabling, recurring jobs will /// be scheduled in parallel under separate connections, increasing the /// throughput. /// public int MaxDegreeOfParallelism { get; set; } /// /// Gets or sets a task scheduler that will be used when parallel scheduling /// is enabled via the option. /// public TaskScheduler TaskScheduler { get; set; } /// public void Execute(BackgroundProcessContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); int jobsProcessed; do { jobsProcessed = EnqueueNextRecurringJobs(context); if (jobsProcessed != 0) { _logger.Debug($"{jobsProcessed} recurring job(s) processed by scheduler."); } } while (jobsProcessed > 0 && !context.IsStopping); if (_pollingDelay > TimeSpan.Zero) { context.Wait(_pollingDelay); } else { var now = _nowFactory(); context.Wait(now.AddMilliseconds(-now.Millisecond).AddSeconds(-now.Second).AddMinutes(1) - now); } } /// public override string ToString() { return GetType().Name; } private int EnqueueNextRecurringJobs(BackgroundProcessContext context) { return UseConnectionDistributedLock(context.Storage, connection => { var jobsProcessed = 0; var now = DateTime.SpecifyKind( !context.Storage.HasFeature(JobStorageFeatures.Connection.GetUtcDateTime) ? _nowFactory() : ((JobStorageConnection)connection).GetUtcDateTime(), DateTimeKind.Utc); if (IsBatchingAvailable(context.Storage, connection)) { var timestamp = JobHelper.ToTimestamp(now); var recurringJobIds = ((JobStorageConnection)connection).GetFirstByLowestScoreFromSet("recurring-jobs", 0, timestamp, BatchSize); if (recurringJobIds != null) { #if !NETSTANDARD1_3 if (MaxDegreeOfParallelism > 1) { Parallel.ForEach( recurringJobIds, new ParallelOptions { MaxDegreeOfParallelism = MaxDegreeOfParallelism, CancellationToken = context.StoppingToken, TaskScheduler = TaskScheduler }, (recurringJobId, state) => { using (var dedicated = context.Storage.GetConnection()) { TryEnqueueBackgroundJob(context, dedicated, recurringJobId, now); } Interlocked.Increment(ref jobsProcessed); }); } else #endif { foreach (var recurringJobId in recurringJobIds) { if (context.IsStopping) break; TryEnqueueBackgroundJob(context, connection, recurringJobId, now); jobsProcessed++; } } } } else { if (MaxDegreeOfParallelism > 1 && !_parallelismIssueLogged) { _logger.Warn("Parallel execution is configured but can't be used, because current storage implementation doesn't support batching."); _parallelismIssueLogged = true; } for (var i = 0; i < BatchSize; i++) { if (context.IsStopping) break; var timestamp = JobHelper.ToTimestamp(now); var recurringJobId = connection.GetFirstByLowestScoreFromSet("recurring-jobs", 0, timestamp); if (recurringJobId == null) break; TryEnqueueBackgroundJob(context, connection, recurringJobId, now); jobsProcessed++; } } return jobsProcessed; }); } private void TryEnqueueBackgroundJob( BackgroundProcessContext context, IStorageConnection connection, string recurringJobId, DateTime now) { using (connection.AcquireDistributedRecurringJobLock(recurringJobId, LockTimeout)) { var recurringJob = connection.GetRecurringJob(recurringJobId); if (recurringJob == null) { RemoveRecurringJob(connection, recurringJobId); return; } ScheduleRecurringJob(context, connection, recurringJobId, recurringJob, now); } } private void ScheduleRecurringJob(BackgroundProcessContext context, IStorageConnection connection, string recurringJobId, RecurringJobEntity recurringJob, DateTime now) { // We always start a transaction, regardless our recurring job was updated or not, // to prevent from infinite loop, when there's an old processing server (pre-1.7.0) // in our environment that doesn't know it should modify the score for entries in // the recurring jobs set. using (var transaction = connection.CreateWriteTransaction()) { Exception exception = null; try { // We can't handle recurring job with unsupported versions - there may be additional // features. we don't know about. We also shouldn't stop the whole scheduler as // there may be jobs with lower versions. Instead, we'll re-schedule such a job and // emit a warning message to the log. if (recurringJob.Version.HasValue && recurringJob.Version > MaxSupportedVersion) { throw new NotSupportedException($"Server '{context.ServerId}' can't process recurring job '{recurringJobId}' of version '{recurringJob.Version ?? 1}'. Max supported version of this server is '{MaxSupportedVersion}'."); } var backgroundJobs = new List(); var precision = _pollingDelay + _pollingDelay; var executions = recurringJob.ScheduleNext( _timeZoneResolver, recurringJob.LastExecution ?? recurringJob.CreatedAt?.AddSeconds(-1) ?? now.AddSeconds(-1), now, precision); foreach (var execution in executions) { var backgroundJob = _factory.TriggerRecurringJob( context.Storage, connection, _profiler, recurringJob, execution); if (!String.IsNullOrEmpty(backgroundJob?.Id)) { backgroundJobs.Add(backgroundJob); } else { _logger.Debug($"Recurring job '{recurringJobId}' execution at '{execution}' has been canceled."); } } foreach (var backgroundJob in backgroundJobs) { _factory.StateMachine.EnqueueBackgroundJob( context.Storage, connection, transaction, recurringJob, backgroundJob, "Triggered by recurring job scheduler", _profiler); } recurringJob.RetryAttempt = 0; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { exception = ex; } if (exception != null) { RetryRecurringJob(recurringJobId, recurringJob, now, exception); } recurringJob.IsChanged(now, out var changedFields); transaction.UpdateRecurringJob(recurringJob, changedFields, _logger); // We should commit transaction outside of the internal try/catch block, because these // exceptions are always due to network issues, and in case of a timeout exception we // can't determine whether it was actually succeeded or not, and can't perform an // idempotent retry in this case. transaction.Commit(); } } private void RetryRecurringJob(string recurringJobId, RecurringJobEntity recurringJob, DateTime now, Exception error) { var errorString = error.ToStringWithOriginalStackTrace(States.FailedState.MaxLinesInExceptionDetails, includeFileInfo: false); if (recurringJob.RetryAttempt < MaxRetryAttemptCount) { var delay = _pollingDelay > TimeSpan.Zero ? _pollingDelay : TimeSpan.FromMinutes(1); _logger.WarnException( $"Recurring job '{recurringJobId}' can't be scheduled due to an error and will be retried in {delay}.", error); recurringJob.ScheduleRetry(now.Add(delay), errorString); } else { _logger.ErrorException( $"Recurring job '{recurringJobId}' can't be scheduled due to an error and will be disabled.", error); recurringJob.Disable(errorString); } } private void RemoveRecurringJob(IStorageConnection connection, string recurringJobId) { _logger.Debug($"Recurring job '{recurringJobId}' doesn't exist and will be removed from schedule."); using (var transaction = connection.CreateWriteTransaction()) { transaction.RemoveFromSet("recurring-jobs", recurringJobId); transaction.Commit(); } } private T UseConnectionDistributedLock(JobStorage storage, Func action) { var resource = "recurring-jobs:lock"; try { using (var connection = storage.GetConnection()) using (connection.AcquireDistributedLock(resource, LockTimeout)) { return action(connection); } } catch (DistributedLockTimeoutException e) when (e.Resource.EndsWith(resource, StringComparison.Ordinal)) { // DistributedLockTimeoutException here doesn't mean that recurring jobs weren't scheduled. // It just means another Hangfire server did this work. _logger.Log( LogLevel.Debug, () => $@"An exception was thrown during acquiring distributed lock the {resource} resource within {LockTimeout.TotalSeconds} seconds. The recurring jobs have not been handled this time.", e); } return default; } private bool IsBatchingAvailable(JobStorage storage, IStorageConnection connection) { if (storage.HasFeature(JobStorageFeatures.Connection.BatchedGetFirstByLowest) || storage.HasFeature("BatchedGetFirstByLowestScoreFromSet")) // FROM RCs { return true; } return _isBatchingAvailableCache.GetOrAdd( connection.GetType(), type => { if (connection is JobStorageConnection storageConnection) { try { storageConnection.GetFirstByLowestScoreFromSet(null, 0, 0, 1); } catch (ArgumentNullException ex) when (ex.ParamName == "key") { return true; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // } } return false; }); } } } ================================================ FILE: src/Hangfire.Core/Server/ServerContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Server { public class ServerContext { public ServerContext() { Queues = []; WorkerCount = -1; } public int WorkerCount { get; set; } public string[] Queues { get; set; } } } ================================================ FILE: src/Hangfire.Core/Server/ServerExceptionContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Server { /// /// Provides the context for the /// method of the interface. /// public class ServerExceptionContext : PerformContext { public ServerExceptionContext( PerformContext context, Exception exception) : base(context) { Exception = exception; } /// /// Gets an exception that occurred during the performance of the job. /// public Exception Exception { get; } /// /// Gets or sets a value that indicates that this /// object handles an exception occurred during the performance of the job. /// public bool ExceptionHandled { get; set; } } } ================================================ FILE: src/Hangfire.Core/Server/ServerHeartbeatProcess.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Diagnostics; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Storage; namespace Hangfire.Server { internal sealed class ServerHeartbeatProcess : IBackgroundProcess { private readonly ILog _logger = LogProvider.GetLogger(typeof(ServerHeartbeatProcess)); private readonly TimeSpan _interval; private readonly TimeSpan _serverTimeout; private readonly Action _requestRestart; private Stopwatch _faultedSince; public ServerHeartbeatProcess(TimeSpan interval, TimeSpan serverTimeout, Action requestRestart) { _interval = interval; _serverTimeout = serverTimeout; _requestRestart = requestRestart; } public void Execute(BackgroundProcessContext context) { _logger.Trace($"{BackgroundServerProcess.GetServerTemplate(context.ServerId)} waiting for {_interval} delay before sending a heartbeat"); context.ShutdownToken.WaitOrThrow(_interval); try { using (var connection = context.Storage.GetConnection()) { connection.Heartbeat(context.ServerId); } if (_faultedSince == null) { _logger.Debug($"{BackgroundServerProcess.GetServerTemplate(context.ServerId)} heartbeat successfully sent"); } else { _logger.Info($"{BackgroundServerProcess.GetServerTemplate(context.ServerId)} is now able to continue sending heartbeats"); _faultedSince = null; } } catch (BackgroundServerGoneException) { if (!context.ShutdownToken.IsCancellationRequested) { _logger.Warn($"{BackgroundServerProcess.GetServerTemplate(context.ServerId)} was considered dead by other servers, restarting..."); _requestRestart(); } return; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.WarnException($"{BackgroundServerProcess.GetServerTemplate(context.ServerId)} encountered an exception while sending heartbeat", ex); if (_faultedSince == null) _faultedSince = Stopwatch.StartNew(); if (_faultedSince.Elapsed >= _serverTimeout) { _logger.Error($"{BackgroundServerProcess.GetServerTemplate(context.ServerId)} will be restarted due to server time out"); _requestRestart(); return; } } } } } ================================================ FILE: src/Hangfire.Core/Server/ServerJobCancellationToken.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; using Hangfire.Annotations; using Hangfire.States; using Hangfire.Storage; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; namespace Hangfire.Server { internal sealed class ServerJobCancellationToken : IJobCancellationToken, IDisposable { private static readonly ConcurrentDictionary> WatchedServers = new ConcurrentDictionary>(); private readonly object _syncRoot = new object(); private readonly string _jobId; private readonly string _serverId; private readonly string _workerId; private readonly IStorageConnection _connection; private readonly CancellationToken _shutdownToken; private readonly Lazy _cancellationTokenHolder; private readonly ConcurrentDictionary _watchedTokens; private bool _disposed; public ServerJobCancellationToken( [NotNull] IStorageConnection connection, [NotNull] string jobId, [NotNull] string serverId, [NotNull] string workerId, CancellationToken shutdownToken) { _jobId = jobId ?? throw new ArgumentNullException(nameof(jobId)); _serverId = serverId ?? throw new ArgumentNullException(nameof(serverId)); _workerId = workerId ?? throw new ArgumentNullException(nameof(workerId)); _connection = connection ?? throw new ArgumentNullException(nameof(connection)); _shutdownToken = shutdownToken; _cancellationTokenHolder = new Lazy( () => new CancellationTokenHolder(_shutdownToken), LazyThreadSafetyMode.None); if (WatchedServers.TryGetValue(_serverId, out _watchedTokens)) { _watchedTokens.TryAdd(this, null); } } public void Dispose() { lock (_syncRoot) { if (_disposed) return; _disposed = true; _watchedTokens?.TryRemove(this, out _); if (_cancellationTokenHolder.IsValueCreated) { _cancellationTokenHolder.Value.Dispose(); } } } [System.Diagnostics.CodeAnalysis.SuppressMessage("SonarLint", "S4275:GettersAndSettersShouldAccessTheExpectedFields", Justification = "Bad property naming for backwards compatibility.")] public CancellationToken ShutdownToken { get { lock (_syncRoot) { CheckDisposed(); return _cancellationTokenHolder.Value.CancellationToken; } } } public bool IsAborted { get { lock (_syncRoot) { CheckDisposed(); return _cancellationTokenHolder.IsValueCreated && _cancellationTokenHolder.Value.IsAborted; } } } public void ThrowIfCancellationRequested() { lock (_syncRoot) { CheckDisposed(); _shutdownToken.ThrowIfCancellationRequested(); if (_cancellationTokenHolder.IsValueCreated && _cancellationTokenHolder.Value.IsAborted) { throw new JobAbortedException(); } // TODO: Create a new connection instead to avoid possible race conditions due to user code if (CheckJobStateChanged(_connection)) { throw new JobAbortedException(); } } } public static void AddServer(string serverId) { WatchedServers.TryAdd(serverId, new ConcurrentDictionary()); } public static void RemoveServer(string serverId) { WatchedServers.TryRemove(serverId, out _); } public static IEnumerable> CheckAllCancellationTokens( string serverId, IStorageConnection connection, CancellationToken cancellationToken) { if (WatchedServers.TryGetValue(serverId, out var watchedTokens)) { var result = new List>(); foreach (var token in watchedTokens) { cancellationToken.ThrowIfCancellationRequested(); if (token.Key.TryCheckJobIsAborted(connection)) { result.Add(Tuple.Create(token.Key._jobId, token.Key._workerId)); } } return result; } return Enumerable.Empty>(); } public bool TryCheckJobIsAborted(IStorageConnection connection) { // Returns `true` only when check was performed AND the job is // aborted AND it was not already aborted by calling this method. // When return value is `false`, this means either the check // wasn't performed (because object is disposed, or there's no // associated token holder) or job is still running. lock (_syncRoot) { if (_disposed || !_cancellationTokenHolder.IsValueCreated || _cancellationTokenHolder.Value.IsAborted) { return false; } return CheckJobStateChanged(connection); } } private bool CheckJobStateChanged(IStorageConnection connection) { if (IsJobStateChanged(connection)) { _cancellationTokenHolder.Value.Abort(); return true; } return false; } private bool IsJobStateChanged(IStorageConnection connection) { var state = connection.GetStateData(_jobId); if (state == null || !state.Name.Equals(ProcessingState.StateName, StringComparison.OrdinalIgnoreCase)) { return true; } if (!state.Data.TryGetValue("ServerId", out var serverId) || !serverId.Equals(_serverId, StringComparison.OrdinalIgnoreCase)) { return true; } if (!state.Data.TryGetValue("WorkerId", out var workerId) || !workerId.Equals(_workerId, StringComparison.OrdinalIgnoreCase)) { return true; } return false; } private void CheckDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().FullName); } } private sealed class CancellationTokenHolder : IDisposable { private readonly CancellationTokenSource _abortedTokenSource; private readonly CancellationTokenSource _linkedTokenSource; public CancellationTokenHolder(CancellationToken shutdownToken) { _abortedTokenSource = new CancellationTokenSource(); _linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(shutdownToken, _abortedTokenSource.Token); } public CancellationToken CancellationToken => _linkedTokenSource.Token; public bool IsAborted => _abortedTokenSource.IsCancellationRequested; public void Abort() { _abortedTokenSource.Cancel(); } public void Dispose() { _linkedTokenSource.Dispose(); _abortedTokenSource.Dispose(); } } } } ================================================ FILE: src/Hangfire.Core/Server/ServerJobCancellationWatcher.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Logging; namespace Hangfire.Server { internal sealed class ServerJobCancellationWatcher : IBackgroundProcess { public static readonly TimeSpan DefaultCheckInterval = TimeSpan.FromSeconds(5); private readonly ILog _logger = LogProvider.GetLogger(typeof(ServerJobCancellationWatcher)); private readonly TimeSpan _checkInterval; public ServerJobCancellationWatcher(TimeSpan checkInterval) { _checkInterval = checkInterval; } public void Execute(BackgroundProcessContext context) { _logger.Trace("Checking for aborted jobs..."); using (var connection = context.Storage.GetConnection()) { var abortedJobIds = ServerJobCancellationToken.CheckAllCancellationTokens( context.ServerId, connection, context.StoppedToken); var aborted = false; foreach (var abortedJobId in abortedJobIds) { _logger.Debug($"Job {abortedJobId.Item1} was aborted on worker {abortedJobId.Item2}."); aborted = true; } if (!aborted) { _logger.Trace("No newly aborted jobs found."); } } context.Wait(_checkInterval); } public override string ToString() { return GetType().Name; } } } ================================================ FILE: src/Hangfire.Core/Server/ServerProcessDispatcherBuilder.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading; using Hangfire.Annotations; using Hangfire.Processing; #pragma warning disable 618 namespace Hangfire.Server { internal sealed class ServerProcessDispatcherBuilder : IBackgroundProcessDispatcherBuilder { private readonly IServerComponent _component; private readonly Func> _threadFactory; public ServerProcessDispatcherBuilder( [NotNull] IServerComponent component, [NotNull] Func> threadFactory) { if (component == null) throw new ArgumentNullException(nameof(component)); if (threadFactory == null) throw new ArgumentNullException(nameof(threadFactory)); _component = component; _threadFactory = threadFactory; } public IBackgroundDispatcher Create(BackgroundServerContext context, BackgroundProcessingServerOptions options) { if (context == null) throw new ArgumentNullException(nameof(context)); if (options == null) throw new ArgumentNullException(nameof(options)); return new BackgroundDispatcher( new BackgroundExecution(new BackgroundExecutionOptions { Name = _component.GetType().Name, RetryDelay = options.RetryDelay }, context.StoppingToken), ExecuteComponent, Tuple.Create(_component, context), _threadFactory); } public override string ToString() { return _component.GetType().Name; } private static void ExecuteComponent(Guid executionId, object state) { var tuple = (Tuple)state; tuple.Item1.Execute(tuple.Item2.StoppingToken); } } } ================================================ FILE: src/Hangfire.Core/Server/ServerWatchdog.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Logging; namespace Hangfire.Server { internal sealed class ServerWatchdog : IBackgroundProcess { public static readonly TimeSpan DefaultCheckInterval = TimeSpan.FromMinutes(5); public static readonly TimeSpan DefaultServerTimeout = TimeSpan.FromMinutes(5); public static readonly TimeSpan MaxServerTimeout = TimeSpan.FromHours(24); public static readonly TimeSpan MaxServerCheckInterval = TimeSpan.FromHours(24); public static readonly TimeSpan MaxHeartbeatInterval = TimeSpan.FromHours(24); private readonly ILog _logger = LogProvider.For(); private readonly TimeSpan _checkInterval; private readonly TimeSpan _serverTimeout; public ServerWatchdog(TimeSpan checkInterval, TimeSpan serverTimeout) { _checkInterval = checkInterval; _serverTimeout = serverTimeout; } public void Execute(BackgroundProcessContext context) { using (var connection = context.Storage.GetConnection()) { var serversRemoved = connection.RemoveTimedOutServers(_serverTimeout); if (serversRemoved != 0) { _logger.Info($"{serversRemoved} servers were removed due to timeout"); } } context.Wait(_checkInterval); } public override string ToString() { return GetType().Name; } } } ================================================ FILE: src/Hangfire.Core/Server/Worker.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; namespace Hangfire.Server { /// /// Represents a background process responsible for processing /// fire-and-forget jobs. /// /// /// /// This is the heart of background processing in Hangfire /// /// /// /// /// public class Worker : IBackgroundProcess { [Obsolete("Please use JobStorageFeatures.StorageTransactionalAcknowledge instead.")] public static readonly string TransactionalAcknowledgePrefix = JobStorageFeatures.TransactionalAcknowledgePrefix; private static readonly ConcurrentDictionary WorkerGuidCache = new(); private static readonly string[] EligibleWorkerStates = new[] { EnqueuedState.StateName, ScheduledState.StateName, ProcessingState.StateName }; private static readonly string[] ProcessingStateArray = new[] { ProcessingState.StateName }; private readonly TimeSpan _jobInitializationWaitTimeout; private readonly int _maxStateChangeAttempts; private readonly ILog _logger = LogProvider.For(); private readonly IEnumerable _queues; private readonly IBackgroundJobPerformer _performer; private readonly IBackgroundJobStateChanger _stateChanger; private readonly IProfiler _profiler; public Worker() : this(EnqueuedState.DefaultQueue) { } public Worker([NotNull] params string[] queues) : this(queues, new BackgroundJobPerformer(), new BackgroundJobStateChanger()) { } public Worker( [NotNull] IEnumerable queues, [NotNull] IBackgroundJobPerformer performer, [NotNull] IBackgroundJobStateChanger stateChanger) : this(queues, performer, stateChanger, jobInitializationTimeout: TimeSpan.FromMinutes(1), maxStateChangeAttempts: 10) { } internal Worker( [NotNull] IEnumerable queues, [NotNull] IBackgroundJobPerformer performer, [NotNull] IBackgroundJobStateChanger stateChanger, TimeSpan jobInitializationTimeout, int maxStateChangeAttempts) { if (queues == null) throw new ArgumentNullException(nameof(queues)); if (performer == null) throw new ArgumentNullException(nameof(performer)); if (stateChanger == null) throw new ArgumentNullException(nameof(stateChanger)); _queues = queues; _performer = performer; _stateChanger = stateChanger; _jobInitializationWaitTimeout = jobInitializationTimeout; _maxStateChangeAttempts = maxStateChangeAttempts; _profiler = new SlowLogProfiler(_logger); } /// public void Execute(BackgroundProcessContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); using (var connection = context.Storage.GetConnection()) using (var fetchedJob = connection.FetchNextJob(_queues.ToArray(), context.StoppingToken)) { var requeueOnException = true; try { BackgroundJob backgroundJob = null; using (var timeoutCts = new CancellationTokenSource(_jobInitializationWaitTimeout)) using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( context.StoppingToken, timeoutCts.Token)) { var processingState = new ProcessingState(context.ServerId, WorkerGuidCache.GetOrAdd(context.ExecutionId, static guid => guid.ToString())); var appliedState = TryChangeState( context, connection, fetchedJob.JobId, processingState, null, EligibleWorkerStates, null, out backgroundJob, linkedCts.Token, context.StoppingToken); // Cancel job processing if the job could not be loaded, was not in the initial state expected // or if a job filter changed the state to something other than processing state if (appliedState == null || !appliedState.Name.Equals(ProcessingState.StateName, StringComparison.OrdinalIgnoreCase)) { // We should forget a job in a wrong state, or when timeout exceeded. requeueOnException = false; fetchedJob.RemoveFromQueue(); return; } } // Checkpoint #3. Job is in the Processing state. However, there are // no guarantees that it was performed. We need to re-queue it even // it was performed to guarantee that it was performed AT LEAST once. // It will be re-queued after the JobTimeout was expired. var state = PerformJob(context, connection, fetchedJob.JobId, backgroundJob, out var customData); var transactionalAck = context.Storage.HasFeature(JobStorageFeatures.Transaction.RemoveFromQueue(fetchedJob.GetType())); if (state != null) { // Ignore return value, because we should not do anything when current state is not Processing. TryChangeState( context, connection, fetchedJob.JobId, state, customData, ProcessingStateArray, transactionalAck ? fetchedJob : null, out _, CancellationToken.None, context.ShutdownToken); } // Checkpoint #4. The job was performed, and it is in the one // of the explicit states (Succeeded, Scheduled and so on). // It should not be re-queued, but we still need to remove its // processing information. requeueOnException = false; fetchedJob.RemoveFromQueue(); // Success point. No things must be done after previous command // was succeeded. } catch (Exception ex) when (ex.IsCatchableExceptionType()) { if (context.IsStopping) { var action = requeueOnException ? "It will be re-queued" : "It will be removed from queue later"; _logger.Warn($"Worker stop requested while processing background job '{fetchedJob.JobId}'. {action}."); } if (requeueOnException) { Requeue(fetchedJob); } throw; } } } private IState TryChangeState( BackgroundProcessContext context, IStorageConnection connection, string jobId, IState state, IReadOnlyDictionary customData, string[] expectedStates, IFetchedJob completeJob, out BackgroundJob backgroundJob, CancellationToken initializeToken, CancellationToken abortToken) { Exception exception = null; abortToken.ThrowIfCancellationRequested(); // At least one retry attempt should always be performed. var maxRetryAttempts = _maxStateChangeAttempts > 0 ? _maxStateChangeAttempts : 1; for (var retryAttempt = 0; retryAttempt < maxRetryAttempts; retryAttempt++) { try { var stateChangeContext = new StateChangeContext( context.Storage, connection, null, jobId, state, expectedStates, disableFilters: false, completeJob, initializeToken, _profiler, context.ServerId, customData); var resultingState = _stateChanger.ChangeState(stateChangeContext); backgroundJob = stateChangeContext.ProcessedJob; return resultingState; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.DebugException( $"State change attempt {retryAttempt + 1} of {_maxStateChangeAttempts} failed due to an error, see inner exception for details", ex); exception = ex; } abortToken.WaitOrThrow(TimeSpan.FromSeconds(retryAttempt)); } _logger.ErrorException( $"{_maxStateChangeAttempts} state change attempt(s) failed due to an exception, moving job to the FailedState", exception); var failedStateContext = new StateChangeContext( context.Storage, connection, null, jobId, new FailedState(exception, context.ServerId) { Reason = $"Failed to change state to a '{state.Name}' one due to an exception after {_maxStateChangeAttempts} retry attempts" }, expectedStates, disableFilters: true, completeJob, initializeToken, _profiler, context.ServerId); var failedResult = _stateChanger.ChangeState(failedStateContext); backgroundJob = failedStateContext.ProcessedJob; return failedResult; } private void Requeue(IFetchedJob fetchedJob) { try { fetchedJob.Requeue(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.WarnException($"Failed to immediately re-queue the background job '{fetchedJob.JobId}'. Next invocation may be delayed, if invisibility timeout is used", ex); } } private IState PerformJob( BackgroundProcessContext context, IStorageConnection connection, string jobId, BackgroundJob backgroundJob, out IReadOnlyDictionary customData) { customData = null; try { if (backgroundJob == null) { var jobData = connection.GetJobData(jobId); if (jobData == null) { // Job expired just after moving to a processing state. This is an // unreal scenario, but shit happens. Returning null instead of throwing // an exception and rescuing from en-queueing a poisoned jobId back // to a queue. return null; } jobData.EnsureLoaded(); backgroundJob = new BackgroundJob(jobId, jobData.Job, jobData.CreatedAt, jobData.ParametersSnapshot); } using (var jobToken = new ServerJobCancellationToken(connection, backgroundJob.Id, context.ServerId, WorkerGuidCache.GetOrAdd(context.ExecutionId, static guid => guid.ToString()), context.StoppedToken)) { var performContext = new PerformContext(context.Storage, connection, backgroundJob, jobToken, _profiler, context.ServerId, null); var latency = (DateTime.UtcNow - backgroundJob.CreatedAt).TotalMilliseconds; var duration = Stopwatch.StartNew(); var result = _performer.Perform(performContext); duration.Stop(); customData = new Dictionary(performContext.Items); return !performContext.Items.TryGetValue(BackgroundJobPerformer.ContextCanceledKey, out var filter) ? (IState)new SucceededState(result, (long) latency, duration.ElapsedMilliseconds) : new DeletedState { Reason = $"Canceled by filter '{filter}'" }; } } catch (JobAbortedException) { // Background job performance was aborted due to a // state change, so its identifier should be removed // from a queue. return null; } catch (JobPerformanceException ex) { return new FailedState(ex.InnerException, context.ServerId) { Reason = ex.Message }; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { if (ex is OperationCanceledException && context.IsStopped) { throw; } return new FailedState(ex, context.ServerId) { Reason = "An exception occurred during processing of a background job." }; } } } } ================================================ FILE: src/Hangfire.Core/States/ApplyStateContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Profiling; using Hangfire.Storage; namespace Hangfire.States { #pragma warning disable 618 public class ApplyStateContext : StateContext #pragma warning restore 618 { public ApplyStateContext( [NotNull] IWriteOnlyTransaction transaction, [NotNull] ElectStateContext context) : this(context.Storage, context.Connection, transaction, context.BackgroundJob, context.CandidateState, context.CurrentState, context.Profiler, context.StateMachine, context.CustomData != null ? new Dictionary(context.CustomData) : null) { // TODO: Add explicit JobExpirationTimeout parameter in 2.0, because it's unclear it isn't preserved } public ApplyStateContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] IWriteOnlyTransaction transaction, [NotNull] BackgroundJob backgroundJob, [NotNull] IState newState, [CanBeNull] string oldStateName) : this(storage, connection, transaction, backgroundJob, newState, oldStateName, EmptyProfiler.Instance, null) { } internal ApplyStateContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] IWriteOnlyTransaction transaction, [NotNull] BackgroundJob backgroundJob, [NotNull] IState newState, [CanBeNull] string oldStateName, [NotNull] IProfiler profiler, [CanBeNull] IStateMachine stateMachine, [CanBeNull] IReadOnlyDictionary customData = null) { BackgroundJob = backgroundJob ?? throw new ArgumentNullException(nameof(backgroundJob)); Storage = storage ?? throw new ArgumentNullException(nameof(storage)); Connection = connection ?? throw new ArgumentNullException(nameof(connection)); Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); NewState = newState ?? throw new ArgumentNullException(nameof(newState)); OldStateName = oldStateName; Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); StateMachine = stateMachine; CustomData = customData; JobExpirationTimeout = storage.JobExpirationTimeout; } [NotNull] public JobStorage Storage { get; } [NotNull] public IStorageConnection Connection { get; } [NotNull] public IWriteOnlyTransaction Transaction { get; } public override BackgroundJob BackgroundJob { get; } [CanBeNull] public string OldStateName { get; } [NotNull] public IState NewState { get; } public TimeSpan JobExpirationTimeout { get; set; } [NotNull] internal IProfiler Profiler { get; } [CanBeNull] public IReadOnlyDictionary CustomData { get; } [CanBeNull] public IStateMachine StateMachine { get; } public T GetJobParameter([NotNull] string name) => GetJobParameter(name, allowStale: false); public T GetJobParameter([NotNull] string name, bool allowStale) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); try { string value; if (allowStale && BackgroundJob.ParametersSnapshot != null) { BackgroundJob.ParametersSnapshot.TryGetValue(name, out value); } else { value = Connection.GetJobParameter(BackgroundJob.Id, name); } return SerializationHelper.Deserialize(value, SerializationOption.User); } catch (Exception ex) { throw new InvalidOperationException( $"Could not get a value of the job parameter `{name}`. See inner exception for details.", ex); } } } } ================================================ FILE: src/Hangfire.Core/States/AwaitingState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; using Newtonsoft.Json; namespace Hangfire.States { /// /// Defines the intermediate state of a background job when it is waiting /// for a parent background job to be finished before it is moved to the /// by the /// filter. /// /// /// /// Background job in is referred as a /// continuation of a background job with . /// /// /// public class AwaitingState : IState { private static readonly TimeSpan DefaultExpiration = TimeSpan.FromDays(365); /// /// Represents the name of the Awaiting state. This field is read-only. /// /// /// The value of this field is "Awaiting". /// public static readonly string StateName = "Awaiting"; /// /// Initializes a new instance of the class with /// the specified parent background job id and with an instance of the /// class as a next state. /// /// The identifier of a background job to wait for. public AwaitingState([NotNull] string parentId) : this(parentId, new EnqueuedState()) { } /// /// Initializes a new instance of the class with /// the specified parent job id and next state. /// /// The identifier of a background job to wait for. /// The next state for the continuation. // TODO: Warning inconsistency - everywhere else is OnlyOnSucceededState public AwaitingState([NotNull] string parentId, [NotNull] IState nextState) : this(parentId, nextState, JobContinuationOptions.OnAnyFinishedState) { } /// /// Initializes a new instance of the class with /// the given options along with other parameters. /// /// The identifier of a background job to wait for. /// The next state for the continuation. /// Options to configure a continuation. [JsonConstructor] public AwaitingState([NotNull] string parentId, [NotNull] IState nextState, JobContinuationOptions options) : this(parentId, nextState, options, DefaultExpiration) { } /// /// Initializes a new instance of the class with /// the specified expiration time along with other parameters. /// /// The identifier of a background job to wait for. /// The next state for the continuation. /// Options to configure the continuation. /// The expiration time for the continuation. public AwaitingState( [NotNull] string parentId, [NotNull] IState nextState, JobContinuationOptions options, TimeSpan expiration) { if (parentId == null) throw new ArgumentNullException(nameof(parentId)); if (nextState == null) throw new ArgumentNullException(nameof(nextState)); ParentId = parentId; NextState = nextState; Options = options; Expiration = expiration; } /// /// Gets the identifier of a parent background job. /// [NotNull] public string ParentId { get; } /// /// Gets the next state, to which a background job will be moved. /// [NotNull] public IState NextState { get; } /// /// Gets the continuation options associated with the current state. /// public JobContinuationOptions Options { get; } /// /// Gets the expiration time of a background job continuation. /// [JsonIgnore] public TimeSpan Expiration { get; } /// /// /// Always equals to for the . /// Please see the remarks section of the IState.Name /// article for the details. /// [JsonIgnore] public string Name => StateName; /// public string Reason { get; set; } /// /// /// Always returns for the . /// Please refer to the IState.IsFinal documentation /// for the details. /// [JsonIgnore] public bool IsFinal => false; /// /// /// Always returns for the . /// Please see the description of this property in the /// IState.IgnoreJobLoadException /// article. /// [JsonIgnore] public bool IgnoreJobLoadException => false; /// /// /// Returning dictionary contains the following keys. You can obtain /// the state data by using the /// method. /// /// /// Key /// Type /// Deserialize Method /// Notes /// /// /// ParentId /// /// Not required /// Please see the property. /// /// /// NextState /// /// /// with /// /// /// Please see the property. /// /// /// Options /// /// /// with /// /// Please see the property. /// /// /// public Dictionary SerializeData() { var result = new Dictionary { { "ParentId", ParentId }, { "NextState", SerializationHelper.Serialize(NextState, SerializationOption.TypedInternal) } }; if (GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_170)) { result.Add("Options", Options.ToString("D")); } else { result.Add("Options", Options.ToString("G")); result.Add("Expiration", Expiration.ToString()); } return result; } internal sealed class Handler : IStateHandler { public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction) { if (!GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_180) || !context.Storage.HasFeature(JobStorageFeatures.Monitoring.AwaitingJobs)) { transaction.AddToSet("awaiting", context.BackgroundJob.Id, JobHelper.ToTimestamp(DateTime.UtcNow)); } } public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction) { transaction.RemoveFromSet("awaiting", context.BackgroundJob.Id); } // ReSharper disable once MemberHidesStaticFromOuterClass public string StateName => AwaitingState.StateName; } } } ================================================ FILE: src/Hangfire.Core/States/BackgroundJobStateChanger.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Linq; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; namespace Hangfire.States { public class BackgroundJobStateChanger : IBackgroundJobStateChanger { private static readonly TimeSpan JobLockTimeout = TimeSpan.FromMinutes(15); private readonly StateMachine _stateMachine; public BackgroundJobStateChanger() : this(JobFilterProviders.Providers) { } public BackgroundJobStateChanger([NotNull] IJobFilterProvider filterProvider) : this(new StateMachine(filterProvider, new CoreStateMachine())) { } public BackgroundJobStateChanger([NotNull] StateMachine stateMachine) { _stateMachine = stateMachine ?? throw new ArgumentNullException(nameof(stateMachine)); } internal BackgroundJobStateChanger([NotNull] IJobFilterProvider filterProvider, [NotNull] IStateMachine stateMachine) { if (filterProvider == null) throw new ArgumentNullException(nameof(filterProvider)); if (stateMachine == null) throw new ArgumentNullException(nameof(stateMachine)); _stateMachine = new StateMachine(filterProvider, stateMachine); } public IState ChangeState(StateChangeContext context) { // To ensure that job state will be changed only from one of the // specified states, we need to ensure that other users/workers // are not able to change the state of the job during the // execution of this method. To guarantee this behavior, we are // using distributed application locks and rely on fact, that // any state transitions will be made only within a such lock. IDisposable distributedLock = null; if (context.Transaction != null) { context.Transaction.AcquireDistributedJobLock(context.BackgroundJobId, JobLockTimeout); } else { distributedLock = context.Connection.AcquireDistributedJobLock(context.BackgroundJobId, JobLockTimeout); } using (distributedLock) { var jobData = GetJobData(context); if (jobData == null) { return null; } if (context.ExpectedStates != null && !context.ExpectedStates.Contains(jobData.State, StringComparer.OrdinalIgnoreCase)) { return null; } var stateToApply = context.NewState; try { jobData.EnsureLoaded(); } catch (JobLoadException ex) { // This happens when Hangfire couldn't find the target method, // serialized within a background job. There are many reasons // for this case, including refactored code, or a missing // assembly reference due to a mistake or erroneous deployment. // // The problem is that in this case we can't get any filters, // applied at a method or a class level, and we can't proceed // with the state change without breaking a consistent behavior: // in some cases our filters will be applied, and in other ones // will not. if (!stateToApply.IgnoreJobLoadException) { stateToApply = new FailedState(ex.InnerException, context.ServerId) { Reason = $"Can not change the state to '{stateToApply.Name}': target method was not found." }; } } IWriteOnlyTransaction transaction; IDisposable disposableTransaction; if (context.Transaction == null) { disposableTransaction = transaction = context.Connection.CreateWriteTransaction(); } else { transaction = context.Transaction; disposableTransaction = null; } var backgroundJob = new BackgroundJob(context.BackgroundJobId, jobData.Job, jobData.CreatedAt, jobData.ParametersSnapshot); using (disposableTransaction) { var applyContext = new ApplyStateContext( context.Storage, context.Connection, transaction, backgroundJob, stateToApply, jobData.State, context.Profiler, _stateMachine, context.CustomData); // State changing process can fail due to an exception in state filters themselves, // and DisableFilters property will cause state machine to perform a state transition // without calling any filters. This is required when all the other state change // attempts failed and we need to remove such a job from the processing pipeline. // In this case all the filters are ignored, which may lead to confusion, so it's // highly recommended to use the DisableFilters property only when changing state // to the FailedState. var stateMachine = context.DisableFilters ? _stateMachine.InnerStateMachine : _stateMachine; var appliedState = stateMachine.ApplyState(applyContext); if (context.CompleteJob != null) { if (transaction is JobStorageTransaction jobStorageTransaction) { jobStorageTransaction.RemoveFromQueue(context.CompleteJob); } else { throw new InvalidOperationException("Storage transaction class must inherit the " + nameof(JobStorageTransaction) + " class to use transactional acknowledge"); } } if (context.Transaction == null) { transaction.Commit(); } context.ProcessedJob = backgroundJob; return appliedState; } } } private static JobData GetJobData(StateChangeContext context) { // This code was introduced as a fix for an issue, which appeared when an // external queue implementation was used together with a non-linearizable // storage. The problem was likely related to the SQL Azure + Azure ServiceBus // (or RabbitMQ or so) bundle, because the READCOMMITTED_SNAPSHOT_ON setting // is enabled by default there. // // Since external queueing doesn't share the linearization point with the // storage, it is possible that a worker will pick up a background job before // its transaction was committed. Non-linearizable read will simply return // the NULL value instead of waiting for a transaction to be committed. With // this code, we will make several retry attempts to handle this case to wait // on the client side. // // On the other hand, we need to give up after some retry attempt, because // we should also handle the case, when our queue and job storage became // unsynchronized with each other due to failures, manual intervention or so. // Otherwise we will wait forever in this cases, since And there's no way to // make a distinction between a non-linearizable read and the storages, non- // synchronized with each other. // // In recent versions, Hangfire.SqlServer uses query hints to make all the // reads linearizable no matter what, but there may be other storages that // still require this workaround. So we leave this part implemented, but // decrease the number of attempt from infinite to only a few, because most // storages will provide linearizable reads anyway. It's better to have // "hanging" job in the Processing jobs page in the Dashboard UI than use // infinite loop under the hoods - the former case is better discoverable, // and it's not hard to improve the storage implementation. // TODO 2.0: // Eliminate the need of this timeout by placing an explicit requirement to // storage implementations to either have a single linearization point for all // the operations inside a transaction; or make all the reads linearizable and // execute queueing operations after all the other ones in a transaction. for (var retryAttempt = 0; retryAttempt < 5; retryAttempt++) { var jobData = context.Connection.GetJobData(context.BackgroundJobId); // Empty state means our job wasn't moved to any state after its creation. // Such a jobs may be created by internal logic, and those jobs have very // special meaning, thus we shouldn't allow state changer to alter them, // using this class (which can be used by users), leaving this logic to // low level API only, i.e. state machine. // TODO 1.X: // However, we shouldn't wait for the initial state change, because in some // cases (like in batches) it may take days. We should throw an exception // instead, clearly indicating that such a state change is prohibited. There // may be some issues on GitHub, related to the hanging dashboard requests // in this case. if (!String.IsNullOrEmpty(jobData?.State) || context.Storage.LinearizableReads) { return jobData; } // State change can also be requested from user's request processing logic. // There is always a chance it will be issued against a non-existing or an // already expired background job, and a minute wait (or whatever timeout is // used) is completely unnecessary in this case. // // Since waiting is only required when a worker picks up a job, and // cancellation tokens are used only by the Worker class, we can avoid the // unnecessary waiting logic when no cancellation token is passed. if (context.CancellationToken.IsCancellationRequested || context.CancellationToken == CancellationToken.None) { return null; } context.CancellationToken.Wait(TimeSpan.FromSeconds(retryAttempt)); } return null; } } } ================================================ FILE: src/Hangfire.Core/States/CoreStateMachine.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using Hangfire.Annotations; namespace Hangfire.States { internal sealed class CoreStateMachine : IStateMachine { private readonly Func _stateHandlersThunk; public CoreStateMachine() : this(GetStateHandlers) { } internal CoreStateMachine([NotNull] Func stateHandlersThunk) { if (stateHandlersThunk == null) throw new ArgumentNullException(nameof(stateHandlersThunk)); _stateHandlersThunk = stateHandlersThunk; } public IState ApplyState(ApplyStateContext context) { foreach (var handler in _stateHandlersThunk(context.Storage, context.OldStateName)) { handler.Unapply(context, context.Transaction); } context.Transaction.SetJobState(context.BackgroundJob.Id, context.NewState); foreach (var handler in _stateHandlersThunk(context.Storage, context.NewState.Name)) { handler.Apply(context, context.Transaction); } if (context.NewState.IsFinal) { context.Transaction.ExpireJob(context.BackgroundJob.Id, context.JobExpirationTimeout); } else { context.Transaction.PersistJob(context.BackgroundJob.Id); } return context.NewState; } private static StateHandlersCollection GetStateHandlers(JobStorage storage, string stateName) { var globalHandlers = GlobalStateHandlers.Handlers; return new StateHandlersCollection( globalHandlers as List ?? globalHandlers.ToList(), storage.GetStateHandlers(), stateName); } internal readonly struct StateHandlersCollection( List globalHandlers, IEnumerable storageHandlers, string stateName) { public Enumerator GetEnumerator() => new Enumerator(globalHandlers, storageHandlers, stateName); public ref struct Enumerator { private List.Enumerator _globalEnumerator; private readonly IEnumerator _storageEnumerator; private readonly string _stateName; private IStateHandler _current; public Enumerator(List globalHandlers, IEnumerable storageHandlers, string stateName) { _globalEnumerator = globalHandlers.GetEnumerator(); _storageEnumerator = storageHandlers.GetEnumerator(); _stateName = stateName; _current = default; } public bool MoveNext() { while (_globalEnumerator.MoveNext()) { var current = _globalEnumerator.Current!; if (current.StateName.Equals(_stateName, StringComparison.OrdinalIgnoreCase)) { _current = current; return true; } } while (_storageEnumerator.MoveNext()) { var current = _storageEnumerator.Current!; if (current.StateName.Equals(_stateName, StringComparison.OrdinalIgnoreCase)) { _current = current; return true; } } _current = default; return false; } public IStateHandler Current => _current; } } } } ================================================ FILE: src/Hangfire.Core/States/DeletedState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; using Newtonsoft.Json; namespace Hangfire.States { /// /// Defines the final state of a background job when nobody /// is interested whether it was performed or not. /// /// /// Deleted state is used when you are not interested in a processing /// of a background job. This state isn't backed by any background process, /// so when you change a state of the job to the Deleted, only /// expiration time will be set on a job without any additional processing. /// /// /// /// The following example demonstrates how to cancel an enqueued background /// job. Please note that this job may be processed before you change its state. /// This example shows how to create an instance of the /// class and use the method. Please see /// BackgroundJob.Delete /// and BackgroundJobClientExtensions.Delete /// method overloads for simpler API. /// /// /// /// /// /// BackgroundJob.Delete Overload /// BackgroundJobClientExtensions.Delete Overload /// /// /// public class DeletedState : IState { public static readonly Exception DefaultException = new OperationCanceledException(); /// /// Represents the name of the Deleted state. This field is read-only. /// /// /// The value of this field is "Deleted". /// public static readonly string StateName = "Deleted"; /// /// Initializes a new instance of the class. /// public DeletedState() : this(null) { } public DeletedState([CanBeNull] ExceptionInfo exceptionInfo) { ExceptionInfo = exceptionInfo; DeletedAt = DateTime.UtcNow; } [JsonProperty("Ex", NullValueHandling = NullValueHandling.Ignore)] public ExceptionInfo ExceptionInfo { get; } /// /// /// Always equals to for the . /// Please see the remarks section of the IState.Name /// article for the details. /// [JsonIgnore] public string Name => StateName; /// public string Reason { get; set; } /// /// /// Always returns for the . /// Please refer to the IState.IsFinal documentation /// for the details. /// [JsonIgnore] public bool IsFinal => true; /// /// /// Always returns for the . /// Please see the description of this property in the /// IState.IgnoreJobLoadException /// article. /// [JsonIgnore] public bool IgnoreJobLoadException => true; /// /// Gets a date/time when the current state instance was created. /// [JsonIgnore] public DateTime DeletedAt { get; } /// /// /// Returning dictionary contains the following keys. You can obtain /// the state data by using the /// method. /// /// /// Key /// Type /// Deserialize Method /// Notes /// /// /// DeletedAt /// /// /// Please see the property. /// /// /// Exception /// /// with option. /// Can be absent or null. Please see the property. /// /// /// public Dictionary SerializeData() { var result = new Dictionary { { "DeletedAt", JobHelper.SerializeDateTime(DeletedAt) }, }; if (ExceptionInfo != null) { result.Add("Exception", SerializationHelper.Serialize(ExceptionInfo, SerializationOption.User)); } return result; } internal sealed class Handler : IStateHandler { public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction) { transaction.IncrementCounter("stats:deleted"); } public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction) { transaction.DecrementCounter("stats:deleted"); } // ReSharper disable once MemberHidesStaticFromOuterClass public string StateName => DeletedState.StateName; } } } ================================================ FILE: src/Hangfire.Core/States/ElectStateContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Profiling; using Hangfire.Storage; namespace Hangfire.States { #pragma warning disable 618 public class ElectStateContext : StateContext #pragma warning restore 618 { private readonly IList _traversedStates = new List(); private IState _candidateState; public ElectStateContext([NotNull] ApplyStateContext applyContext) : this(applyContext, null) { } public ElectStateContext([NotNull] ApplyStateContext applyContext, [CanBeNull] StateMachine stateMachine) { if (applyContext == null) throw new ArgumentNullException(nameof(applyContext)); BackgroundJob = applyContext.BackgroundJob; _candidateState = applyContext.NewState; Storage = applyContext.Storage; Connection = applyContext.Connection; Transaction = applyContext.Transaction; CurrentState = applyContext.OldStateName; Profiler = applyContext.Profiler; CustomData = applyContext.CustomData?.ToDictionary(static x => x.Key, static x => x.Value); StateMachine = stateMachine; } public override BackgroundJob BackgroundJob { get; } [NotNull] public JobStorage Storage { get; } [NotNull] public IStorageConnection Connection { get; } [NotNull] public IWriteOnlyTransaction Transaction { get; } [NotNull] public IState CandidateState { get => _candidateState; set { if (value == null) { throw new ArgumentNullException(nameof(value), "The CandidateState property can not be set to null."); } if (_candidateState != value) { _traversedStates.Add(_candidateState); _candidateState = value; } } } [CanBeNull] public string CurrentState { get; } [NotNull] public IState[] TraversedStates => _traversedStates.ToArray(); [NotNull] internal IProfiler Profiler { get; } [CanBeNull] public IDictionary CustomData { get; } [CanBeNull] public StateMachine StateMachine { get; } public void SetJobParameter([NotNull] string name, T value) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); Connection.SetJobParameter(BackgroundJob.Id, name, SerializationHelper.Serialize(value, SerializationOption.User)); } public T GetJobParameter([NotNull] string name) => GetJobParameter(name, allowStale: false); public T GetJobParameter([NotNull] string name, bool allowStale) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); try { string value; if (allowStale && BackgroundJob.ParametersSnapshot != null) { BackgroundJob.ParametersSnapshot.TryGetValue(name, out value); } else { value = Connection.GetJobParameter(BackgroundJob.Id, name); } return SerializationHelper.Deserialize(value, SerializationOption.User); } catch (Exception ex) { throw new InvalidOperationException( $"Could not get a value of the job parameter `{name}`. See inner exception for details.", ex); } } } } ================================================ FILE: src/Hangfire.Core/States/EnqueuedState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; using Newtonsoft.Json; namespace Hangfire.States { /// /// Defines the intermediate state of a background job when it is placed /// on a message queue to be processed by the /// background process as soon as possible. /// /// /// Background job in is referred as /// fire-and-forget job. /// Background job identifier is placed on a queue with the given name. When /// a queue name wasn't specified, the name will /// be used. Message queue implementation depends on a current /// instance. /// /// /// The following example demonstrates the creation of a background job in /// . Please see /// BackgroundJob.Enqueue /// and BackgroundJobClientExtensions.Enqueue /// method overloads for simpler API. /// /// /// /// /// The code below implements the retry action for a failed background job. /// /// /// /// /// /// /// BackgroundJob.Enqueue Overload /// BackgroundJobClientExtensions.Enqueue Overload /// BackgroundJobClientExtensions.Create Overload /// /// /// /// /// public class EnqueuedState : IState { /// /// Represents the default queue name. This field is constant. /// /// /// The value of this field is "default". /// public const string DefaultQueue = "default"; /// /// Represents the name of the Enqueued state. This field is read-only. /// /// /// The value of this field is "Enqueued". /// public static readonly string StateName = "Enqueued"; private string _queue; /// /// Initializes a new instance of the class /// with the default queue name. /// public EnqueuedState() : this(DefaultQueue) { } /// /// Initializes a new instance of the class /// with the specified queue name. /// /// The queue name to which a background job identifier will be added. /// /// /// /// /// The argument is , empty or consists only of /// white-space characters. /// /// /// The argument is not a valid queue name. /// [JsonConstructor] public EnqueuedState([CanBeNull] string queue) { queue = queue ?? DefaultQueue; ValidateQueueName(nameof(queue), queue); _queue = queue; EnqueuedAt = DateTime.UtcNow; } /// /// Gets or sets a queue name to which a background job identifier /// will be added. /// /// A queue name that consists only of lowercase letters, digits and /// underscores. /// /// Queue name must consist only of lowercase letters, digits and /// underscores, other characters aren't permitted. Some examples: /// /// "critical" (good) /// "worker_1" (good) /// "documents queue" (bad, whitespace) /// "MyQueue" (bad, capital letters) /// /// /// /// /// The value specified for a set operation is , /// empty or consists only of white-space characters. /// /// /// The value specified for a set operation is not a valid queue name. /// [NotNull] public string Queue { get { return _queue; } set { ValidateQueueName(nameof(value), value); _queue = value; } } /// /// Gets a date/time when the current state instance was created. /// [JsonIgnore] public DateTime EnqueuedAt { get; } /// /// /// Always equals to for the . /// Please see the remarks section of the IState.Name /// article for the details. /// [JsonIgnore] public string Name => StateName; /// public string Reason { get; set; } /// /// /// Always returns for the . /// Please refer to the IState.IsFinal documentation /// for the details. /// [JsonIgnore] public bool IsFinal => false; /// /// /// Always returns for the . /// Please see the description of this property in the /// IState.IgnoreJobLoadException /// article. /// [JsonIgnore] public bool IgnoreJobLoadException => false; /// /// /// Returning dictionary contains the following keys. You can obtain /// the state data by using the /// method. /// /// /// Key /// Type /// Deserialize Method /// Notes /// /// /// EnqueuedAt /// /// /// Please see the property. /// /// /// Queue /// /// Not required /// Please see the property. /// /// /// public Dictionary SerializeData() { return new Dictionary { { "EnqueuedAt", JobHelper.SerializeDateTime(EnqueuedAt) }, { "Queue", Queue } }; } internal static bool TryValidateQueueName([NotNull] string value) { if (String.IsNullOrWhiteSpace(value)) { throw new ArgumentNullException(nameof(value)); } return ValidateQueueNameInner(value); } internal static void ValidateQueueName([InvokerParameterName] string parameterName, [NotNull] string value) { if (String.IsNullOrWhiteSpace(value)) { throw new ArgumentNullException(parameterName); } if (!ValidateQueueNameInner(value)) { throw new ArgumentException( $"The queue name must consist of lowercase letters, digits, underscore, and dash characters only. Given: '{value}'.", parameterName); } } private static bool ValidateQueueNameInner(string value) { foreach (var ch in value) { // ^[a-z0-9_-]+$ if (!((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '_')) { return false; } } return true; } internal sealed class Handler : IStateHandler { public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction) { var enqueuedState = context.NewState as EnqueuedState; if (enqueuedState == null) { throw new InvalidOperationException( $"`{typeof (Handler).FullName}` state handler can be registered only for the Enqueued state."); } if (context.BackgroundJob.Job?.Queue != null && !context.Storage.HasFeature(JobStorageFeatures.JobQueueProperty)) { throw new NotSupportedException("Current storage doesn't support specifying queues directly for a specific job. Please use the QueueAttribute instead."); } transaction.AddToQueue( context.BackgroundJob.Job?.Queue == null || !DefaultQueue.Equals(enqueuedState.Queue, StringComparison.OrdinalIgnoreCase) ? enqueuedState.Queue : context.BackgroundJob.Job.Queue, context.BackgroundJob.Id); } public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction) { } // ReSharper disable once MemberHidesStaticFromOuterClass public string StateName => EnqueuedState.StateName; } } } ================================================ FILE: src/Hangfire.Core/States/FailedState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Newtonsoft.Json; namespace Hangfire.States { /// /// Defines the intermediate state of a background job when its processing /// was interrupted by an exception and it is a developer's responsibility /// to decide what to do with it next. /// /// /// Failed state is used in Hangfire when something went wrong and an exception /// occurred during the background job processing. The primary reason for this state /// is to notify the developers that something went wrong. By default background job /// is moved to the Failed state only after some automatic retries, because the /// filter is enabled by default. /// /// Failed jobs are not expiring and will stay in your current job storage /// forever, increasing its size until you retry or delete them manually. If you /// expect some exceptions, please use the following rules. /// /// Ignore, move to Succeeded state – use the catch /// statement in your code without re-throwing the exception. /// Ignore, move to Deleted state – use the /// with option. /// Re-queue a job – use the with /// option. /// /// /// It is not supposed to use the class in a user /// code unless you are writing state changing filters or new background processing /// rules. /// /// /// /// /// /// /// public class FailedState : IState { internal static int? MaxLinesInExceptionDetails = 100; /// /// Represents the name of the Failed state. This field is read-only. /// /// /// The value of this field is "Failed". /// public static readonly string StateName = "Failed"; /// /// Initializes a new instance of the class /// with the given exception. /// /// Exception that occurred during the background /// job processing. /// /// The /// argument is public FailedState([NotNull] Exception exception) : this(exception, null) { } /// /// Initializes a new instance of the class /// with the given exception and specified server id. /// /// Exception that occurred during the background job processing. /// Server Id on which the exception occurred. /// /// The /// argument is public FailedState([NotNull] Exception exception, [CanBeNull] string serverId) { if (exception == null) throw new ArgumentNullException(nameof(exception)); FailedAt = DateTime.UtcNow; Exception = exception; ServerId = serverId; MaxLinesInStackTrace = MaxLinesInExceptionDetails; } /// /// Gets a date/time when the current state instance was created. /// [JsonIgnore] public DateTime FailedAt { get; } /// /// Gets the exception that occurred during the background job processing. /// public Exception Exception { get; } [JsonIgnore] public bool IncludeFileInfo { get; set; } = true; /// /// Gets the server identifier on which the exception occurred. /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string ServerId { get; } /// /// /// Always equals to for the . /// Please see the remarks section of the IState.Name /// article for the details. /// [JsonIgnore] public string Name => StateName; /// public string Reason { get; set; } /// /// /// Always returns for the . /// Please refer to the IState.IsFinal documentation /// for the details. /// [JsonIgnore] public bool IsFinal => false; /// /// /// Always returns for the . /// Please see the description of this property in the /// IState.IgnoreJobLoadException /// article. /// [JsonIgnore] public bool IgnoreJobLoadException => false; [JsonIgnore] public int? MaxLinesInStackTrace { get; set; } /// /// /// Returning dictionary contains the following keys. You can obtain /// the state data by using the /// method. /// /// /// Key /// Type /// Deserialize Method /// Notes /// /// /// FailedAt /// /// /// Please see the property. /// /// /// ExceptionType /// /// Not required /// The full name of the current exception type. /// /// /// ExceptionMessage /// /// Not required /// Message that describes the current exception. /// /// /// ExceptionDetails /// /// Not required /// String representation of the current exception. /// /// /// public Dictionary SerializeData() { var result = new Dictionary { { "FailedAt", JobHelper.SerializeDateTime(FailedAt) }, { "ExceptionType", Exception.GetType().FullName }, { "ExceptionMessage", Exception.Message }, { "ExceptionDetails", Exception.ToStringWithOriginalStackTrace(MaxLinesInStackTrace, IncludeFileInfo) } }; if (ServerId != null) { result.Add("ServerId", ServerId); } return result; } } } ================================================ FILE: src/Hangfire.Core/States/IApplyStateFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Storage; namespace Hangfire.States { /// /// Provides methods that are required for a state changed filter. /// public interface IApplyStateFilter { /// /// Called after the specified state was applied /// to the job within the given transaction. /// void OnStateApplied( ApplyStateContext context, IWriteOnlyTransaction transaction); /// /// Called when the state with specified state was /// unapplied from the job within the given transaction. /// void OnStateUnapplied( ApplyStateContext context, IWriteOnlyTransaction transaction); } } ================================================ FILE: src/Hangfire.Core/States/IBackgroundJobStateChanger.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.States { public interface IBackgroundJobStateChanger { /// /// Attempts to change the state of a job, respecting any applicable job filters and state handlers. /// /// Null if a constraint has failed, otherwise the final applied state /// Also ensures that the job data can be loaded for this job IState ChangeState(StateChangeContext context); } } ================================================ FILE: src/Hangfire.Core/States/IElectStateFilter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.States { /// /// Defines methods that are required for a state changing filter. /// public interface IElectStateFilter { /// /// Called when the current state of the job is being changed to the /// specified candidate state. /// This state change could be intercepted and the final state could /// be changed through setting the different state in the context /// in an implementation of this method. /// void OnStateElection(ElectStateContext context); } } ================================================ FILE: src/Hangfire.Core/States/IState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using Hangfire.Annotations; namespace Hangfire.States { /// /// Provides the essential members for describing a background job state. /// /// /// Background job processing in Hangfire is all about moving a background job /// from one state to another. States are used to clearly decide what to do /// with a background job. For example, tells /// Hangfire that a job should be processed by a , /// and tells Hangfire that a job should be investigated /// by a developer. /// /// Each state has some essential properties like , /// and custom ones that are exposed through /// the method. Serialized data may be used during /// the processing stage. /// /// Hangfire allows you to define custom states to extend the processing /// pipeline. interface implementation can be used /// to define additional work for a state transition, and /// interface implementation can be /// used to process background jobs in a new state. For example, delayed jobs /// and their , continuations and their /// can be simply moved to an extension package. /// /// /// /// Let's create a new state. Consider you have background jobs that /// throw a transient exception from time to time, and you want to simply /// ignore those exceptions. By default, Hangfire will move a job that threw /// an exception to the , however a job in the failed /// state will live in a Failed jobs page forever, unless we use , /// delete or retry it manually, because the is not /// a final state. /// /// Our new state will look like a , but we /// define the state as a final one, letting Hangfire to expire faulted /// jobs. Please refer to the interface properties to learn /// about their details. /// /// In articles related to and /// interfaces we'll discuss how to use this new state. /// /// /// /// /// /// /// /// public interface IState { /// /// Gets the unique name of the state. /// /// /// Unique among other states string, that is ready for /// ordinal comparisons. /// /// /// The state name is used to differentiate one state from another /// during the state change process. So all the implemented states /// should have a unique state name. Please use one-word names /// that start with a capital letter, in a past tense in English for /// your state names, for example: /// /// Succeeded /// Enqueued /// Deleted /// Failed /// /// /// /// The returning value should be hard-coded, no modifications of /// this property should be allowed to a user. Implementors should /// not add a public setter on this property. /// /// [NotNull] string Name { get; } /// /// Gets the human-readable reason of a state transition. /// /// /// Any string with a reasonable length to fit dashboard elements. /// /// /// The reason is usually displayed in the Dashboard UI to simplify /// the understanding of a background job lifecycle by providing a /// human-readable text that explains why a background job is moved /// to the corresponding state. Here are some examples: /// /// /// Can not change the state to 'Enqueued': target /// method was not found /// /// Exceeded the maximum number of retry attempts /// /// /// The reason value is usually not hard-coded in a state implementation, /// allowing users to change it when creating an instance of a state /// through the public setter. /// /// [CanBeNull] string Reason { get; } /// /// Gets if the current state is a final one. /// /// /// for intermediate states, /// and for the final ones. /// /// /// Final states define a termination stage of a background job /// processing pipeline. Background jobs in a final state is considered /// as finished with no further processing required. /// /// The state machine marks /// finished background jobs to be expired within an interval that /// is defined in the /// property that is available from a state changing filter that /// implements the interface. /// /// /// When implementing this property, always hard-code this property to /// or . Hangfire does /// not work with states that can be both intermediate and /// final yet. Don't define a public setter for this property. /// /// /// /// /// /// bool IsFinal { get; } /// /// Gets whether transition to this state should ignore job de-serialization /// exceptions. /// /// /// to move to the on /// deserialization exceptions, to ignore them. /// /// /// During a state transition, an instance of the class /// is deserialized to get state changing filters, and to allow /// state handlers to perform additional work related to the state. /// /// However we cannot always deserialize a job, for example, when job method was /// removed from the code base or its assembly reference is missing. Since background /// processing is impossible anyway, the state machine /// moves such a background job to the in this case to /// highlight a problem to the developers (because deserialization exception may /// occur due to bad refactorings or other programming mistakes). /// /// However, in some exceptional cases we can ignore deserialization exceptions, /// and allow a state transition for some states that does not require a /// instance. itself and are /// examples of such a behavior. /// /// /// In general, implementers should return when implementing /// this property. /// /// /// /// /// bool IgnoreJobLoadException { get; } /// /// Gets a serialized representation of the current state. /// /// /// Returning dictionary contains the serialized properties of a state. You can obtain /// the state data by using the /// method. Please refer to documentation for this method in implementors to learn /// which key/value pairs are available. /// /// A dictionary with serialized properties of the current state. [NotNull] Dictionary SerializeData(); } } ================================================ FILE: src/Hangfire.Core/States/IStateHandler.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.Storage; namespace Hangfire.States { /// /// Provides a mechanism for performing custom actions when applying or /// unapplying the state of a background job by . /// public interface IStateHandler { /// /// Gets the name of a state, for which custom actions will be /// performed. /// string StateName { get; } /// /// Performs additional actions when applying a state whose name is /// equal to the property. /// /// The context of a state applying process. /// The current transaction of a state applying process. void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction); /// /// Performs additional actions when unapplying a state whose name /// is equal to the property. /// /// The context of a state applying process. /// The current transaction of a state applying process. void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction); } } ================================================ FILE: src/Hangfire.Core/States/IStateMachine.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.States { /// /// Provides a mechanism for running state election and state applying processes. /// /// /// public interface IStateMachine { /// /// Performs the state applying process, where a current background job /// will be moved to the elected state. /// /// The context of a state applying process. IState ApplyState(ApplyStateContext context); } } ================================================ FILE: src/Hangfire.Core/States/ProcessingState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; using Hangfire.Server; using Newtonsoft.Json; namespace Hangfire.States { /// /// Defines the intermediate state of a background job when a /// has started to process it. /// /// /// public class ProcessingState : IState { /// /// Represents the name of the Processing state. This field is read-only. /// /// /// The value of this field is "Processing". /// public static readonly string StateName = "Processing"; internal ProcessingState(string serverId, string workerId) { if (String.IsNullOrWhiteSpace(serverId)) throw new ArgumentNullException(nameof(serverId)); ServerId = serverId; StartedAt = DateTime.UtcNow; WorkerId = workerId; } /// /// Gets a date/time when the current state instance was created. /// [JsonIgnore] public DateTime StartedAt { get; } /// /// Gets the instance id of an instance of the /// class, whose background process started to process an /// enqueued background job. /// /// Usually the string representation of a GUID value, may vary in future versions. public string ServerId { get; } /// /// Gets the identifier of a that started to /// process an enqueued background job. /// public string WorkerId { get; } /// /// /// Always equals to for the . /// Please see the remarks section of the IState.Name /// article for the details. /// [JsonIgnore] public string Name => StateName; /// public string Reason { get; set; } /// /// /// Always returns for the . /// Please refer to the IState.IsFinal documentation /// for the details. /// [JsonIgnore] public bool IsFinal => false; /// /// /// Always returns for the . /// Please see the description of this property in the /// IState.IgnoreJobLoadException /// article. /// [JsonIgnore] public bool IgnoreJobLoadException => false; /// /// /// Returning dictionary contains the following keys. You can obtain /// the state data by using the /// method. /// /// /// Key /// Type /// Deserialize Method /// Notes /// /// /// StartedAt /// /// /// Please see the property. /// /// /// ServerId /// /// Not required /// Please see the property. /// /// /// WorkerId /// /// Not required /// Please see the property. /// /// /// public Dictionary SerializeData() { return new Dictionary { { "StartedAt", JobHelper.SerializeDateTime(StartedAt) }, { "ServerId", ServerId }, { "WorkerId", WorkerId } }; } } } ================================================ FILE: src/Hangfire.Core/States/ScheduledState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; using Hangfire.Server; using Hangfire.Storage; using Newtonsoft.Json; namespace Hangfire.States { /// /// Defines the intermediate state of a background job when it is placed /// on a schedule to be moved to the in the future /// by background process. /// /// /// /// Background job in is referred as /// delayed job. /// /// /// /// The following example demonstrates the creation of a background job that will /// be processed after two hours. Please see BackgroundJob.Schedule /// and BackgroundJobClientExtensions.Schedule /// method overloads for simpler API. /// /// /// /// /// BackgroundJob.Schedule Overload /// BackgroundJobClientExtensions.Schedule Overload /// /// /// /// public class ScheduledState : IState { /// /// Represents the name of the Scheduled state. This field is read-only. /// /// /// The value of this field is "Scheduled". /// public static readonly string StateName = "Scheduled"; /// /// Initializes a new instance of the class /// with the specified time interval after which a job should be moved to /// the . /// /// The time interval after which a job will be /// moved to the . public ScheduledState(TimeSpan enqueueIn) : this(DateTime.UtcNow.Add(enqueueIn)) { } /// /// Initializes a new instance of the /// class with the specified date/time in UTC format when a job should /// be moved to the . /// /// The date/time when a job will be moved to the /// . [JsonConstructor] public ScheduledState(DateTime enqueueAt) { EnqueueAt = enqueueAt; ScheduledAt = DateTime.UtcNow; } /// /// Gets a date/time when a background job should be enqueued. /// /// Any date/time in format. public DateTime EnqueueAt { get; } /// /// Gets a date/time when the current state instance was created. /// [JsonIgnore] public DateTime ScheduledAt { get; } /// /// /// Always equals to for the . /// Please see the remarks section of the IState.Name /// article for the details. /// [JsonIgnore] public string Name => StateName; /// public string Reason { get; set; } /// /// /// Always returns for the . /// Please refer to the IState.IsFinal documentation /// for the details. /// [JsonIgnore] public bool IsFinal => false; /// /// /// Always returns for the . /// Please see the description of this property in the /// IState.IgnoreJobLoadException /// article. /// [JsonIgnore] public bool IgnoreJobLoadException => false; /// /// /// Returning dictionary contains the following keys. You can obtain /// the state data by using the /// method. /// /// /// Key /// Type /// Deserialize Method /// Notes /// /// /// EnqueueAt /// /// /// Please see the property. /// /// /// ScheduledAt /// /// /// Please see the property. /// /// /// public Dictionary SerializeData() { return new Dictionary { { "EnqueueAt", JobHelper.SerializeDateTime(EnqueueAt) }, { "ScheduledAt", JobHelper.SerializeDateTime(ScheduledAt) } }; } internal sealed class Handler : IStateHandler { public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction) { if (!(context.NewState is ScheduledState scheduledState)) { throw new InvalidOperationException( $"`{typeof (Handler).FullName}` state handler can be registered only for the Scheduled state."); } CheckJobQueueFeatureIsSupported(context); transaction.AddToSet( "schedule", context.BackgroundJob.Job?.Queue == null ? context.BackgroundJob.Id : context.BackgroundJob.Job.Queue + ':' + context.BackgroundJob.Id, JobHelper.ToTimestamp(scheduledState.EnqueueAt)); } public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction) { CheckJobQueueFeatureIsSupported(context); transaction.RemoveFromSet( "schedule", context.BackgroundJob.Job?.Queue == null ? context.BackgroundJob.Id : context.BackgroundJob.Job.Queue + ':' + context.BackgroundJob.Id); } // ReSharper disable once MemberHidesStaticFromOuterClass public string StateName => ScheduledState.StateName; private static void CheckJobQueueFeatureIsSupported(ApplyStateContext context) { if (context.BackgroundJob.Job?.Queue != null && !context.Storage.HasFeature(JobStorageFeatures.JobQueueProperty)) { throw new NotSupportedException("Current storage doesn't support specifying queues directly for a specific job. Please use the QueueAttribute instead."); } } } } } ================================================ FILE: src/Hangfire.Core/States/StateChangeContext.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading; using Hangfire.Annotations; using Hangfire.Profiling; using Hangfire.Storage; namespace Hangfire.States { [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Cancellation tokens in this class are used only as a part of a general context and don't have usual meaning.")] public class StateChangeContext { public StateChangeContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] string backgroundJobId, [NotNull] IState newState) : this(storage, connection, backgroundJobId, newState, null) { } public StateChangeContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] string backgroundJobId, [NotNull] IState newState, [CanBeNull] params string[] expectedStates) : this(storage, connection, backgroundJobId, newState, expectedStates, CancellationToken.None) { } public StateChangeContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] string backgroundJobId, [NotNull] IState newState, [CanBeNull] IEnumerable expectedStates, CancellationToken cancellationToken) : this(storage, connection, null, backgroundJobId, newState, expectedStates, false, null, cancellationToken, EmptyProfiler.Instance, null) { } public StateChangeContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [CanBeNull] JobStorageTransaction transaction, [NotNull] string backgroundJobId, [NotNull] IState newState, [CanBeNull] IEnumerable expectedStates, CancellationToken cancellationToken) : this(storage, connection, transaction, backgroundJobId, newState, expectedStates, false, null, cancellationToken, EmptyProfiler.Instance, null) { } internal StateChangeContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [NotNull] string backgroundJobId, [NotNull] IState newState, [CanBeNull] IEnumerable expectedStates, bool disableFilters, CancellationToken cancellationToken, [NotNull] IProfiler profiler, [CanBeNull] string serverId, [CanBeNull] IReadOnlyDictionary customData = null) : this(storage, connection, null, backgroundJobId, newState, expectedStates, disableFilters, null, cancellationToken, profiler, serverId, customData) { } internal StateChangeContext( [NotNull] JobStorage storage, [NotNull] IStorageConnection connection, [CanBeNull] JobStorageTransaction transaction, [NotNull] string backgroundJobId, [NotNull] IState newState, [CanBeNull] IEnumerable expectedStates, bool disableFilters, [CanBeNull] IFetchedJob completeJob, CancellationToken cancellationToken, [NotNull] IProfiler profiler, [CanBeNull] string serverId, [CanBeNull] IReadOnlyDictionary customData = null) { Storage = storage ?? throw new ArgumentNullException(nameof(storage)); Connection = connection ?? throw new ArgumentNullException(nameof(connection)); Transaction = transaction; BackgroundJobId = backgroundJobId ?? throw new ArgumentNullException(nameof(backgroundJobId)); NewState = newState ?? throw new ArgumentNullException(nameof(newState)); ExpectedStates = expectedStates; DisableFilters = disableFilters; CancellationToken = cancellationToken; Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); ServerId = serverId; CustomData = customData; CompleteJob = completeJob; } [NotNull] public JobStorage Storage { get; } [NotNull] public IStorageConnection Connection { get; } [CanBeNull] public JobStorageTransaction Transaction { get; } [NotNull] public string BackgroundJobId { get; } [NotNull] public IState NewState { get; } [CanBeNull] public IEnumerable ExpectedStates { get; } public bool DisableFilters { get; } public CancellationToken CancellationToken { get; } [NotNull] internal IProfiler Profiler { get; } [CanBeNull] public IReadOnlyDictionary CustomData { get; } [CanBeNull] public IFetchedJob CompleteJob { get; } [CanBeNull] public BackgroundJob ProcessedJob { get; set; } [CanBeNull] public string ServerId { get; set; } } } ================================================ FILE: src/Hangfire.Core/States/StateHandlerCollection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Hangfire.States { [SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Public API, can not change in minor versions.")] [Obsolete("This was a helper class, and it's not used anymore. Will be removed in 2.0.0.")] public class StateHandlerCollection { private readonly Dictionary> _handlers = new Dictionary>(); public void AddRange(IEnumerable handlers) { if (handlers == null) throw new ArgumentNullException(nameof(handlers)); foreach (var handler in handlers) { AddHandler(handler); } } public void AddHandler(IStateHandler handler) { if (handler == null) throw new ArgumentNullException(nameof(handler)); if (handler.StateName == null) throw new ArgumentException("The StateName property of the given state handler must be non null.", nameof(handler)); if (!_handlers.TryGetValue(handler.StateName, out var handlers)) { _handlers.Add(handler.StateName, handlers = new List()); } handlers.Add(handler); } public IEnumerable GetHandlers(string stateName) { if (stateName == null || !_handlers.TryGetValue(stateName, out var handlers)) { return Enumerable.Empty(); } return handlers.ToArray(); } } } ================================================ FILE: src/Hangfire.Core/States/StateMachine.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Profiling; namespace Hangfire.States { // TODO: Merge this class with BackgroundJobStateChanger in 2.0.0 public class StateMachine : IStateMachine { private readonly IJobFilterProvider _filterProvider; private readonly IStateMachine _innerStateMachine; public StateMachine([NotNull] IJobFilterProvider filterProvider) : this(filterProvider, new CoreStateMachine()) { } internal StateMachine( [NotNull] IJobFilterProvider filterProvider, [NotNull] IStateMachine innerStateMachine) { if (filterProvider == null) throw new ArgumentNullException(nameof(filterProvider)); if (innerStateMachine == null) throw new ArgumentNullException(nameof(innerStateMachine)); _filterProvider = filterProvider; _innerStateMachine = innerStateMachine; } public IStateMachine InnerStateMachine => _innerStateMachine; [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Parameter name changed to avoid ambiguity and errors in the method's implementation.")] public IState ApplyState(ApplyStateContext initialContext) { if (initialContext == null) throw new ArgumentNullException(nameof(initialContext)); var filterInfo = GetFilters(initialContext.BackgroundJob.Job); var electFilters = filterInfo.ElectStateFilters; var applyFilters = filterInfo.ApplyStateFilters; // Electing a a state var electContext = new ElectStateContext(initialContext, this); foreach (var filter in electFilters) { electContext.Profiler.InvokeMeasured( new KeyValuePair(filter, electContext), InvokeOnStateElection, static ctx => $"OnStateElection for {ctx.Value.BackgroundJob.Id}"); } foreach (var state in electContext.TraversedStates) { initialContext.Transaction.AddJobState(electContext.BackgroundJob.Id, state); } // Applying the elected state var context = new ApplyStateContext(initialContext.Transaction, electContext) { JobExpirationTimeout = initialContext.JobExpirationTimeout }; foreach (var filter in applyFilters) { context.Profiler.InvokeMeasured( new KeyValuePair(filter, context), InvokeOnStateUnapplied, static ctx => $"OnStateUnapplied for {ctx.Value.BackgroundJob.Id}"); } foreach (var filter in applyFilters) { context.Profiler.InvokeMeasured( new KeyValuePair(filter, context), InvokeOnStateApplied, static ctx => $"OnStateApplied for {ctx.Value.BackgroundJob.Id}"); } return _innerStateMachine.ApplyState(context); } private static void InvokeOnStateElection(KeyValuePair x) { try { x.Key.OnStateElection(x.Value); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ex.PreserveOriginalStackTrace(); throw; } } private static void InvokeOnStateApplied(KeyValuePair x) { try { x.Key.OnStateApplied(x.Value, x.Value.Transaction); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ex.PreserveOriginalStackTrace(); throw; } } private static void InvokeOnStateUnapplied(KeyValuePair x) { try { x.Key.OnStateUnapplied(x.Value, x.Value.Transaction); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ex.PreserveOriginalStackTrace(); throw; } } private JobFilterInfo GetFilters(Job job) { return new JobFilterInfo(_filterProvider.GetFilters(job)); } } } ================================================ FILE: src/Hangfire.Core/States/SucceededState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Globalization; using Hangfire.Common; using Hangfire.Storage; using Newtonsoft.Json; namespace Hangfire.States { /// /// Defines the final state of a background job when a /// performed an enqueued job without any exception thrown during the performance. /// /// /// All the transitions to the Succeeded state are internal for the /// background process. You can't create background jobs using this state, and can't change state /// to Succeeded. /// This state is used in a user code primarily in state change filters (TODO: add a link) /// to add custom logic during state transitions. /// /// /// /// /// /// /// public class SucceededState : IState { /// /// Represents the name of the Succeeded state. This field is read-only. /// /// /// The value of this field is "Succeeded". /// public static readonly string StateName = "Succeeded"; [JsonConstructor] public SucceededState(object result, long latency, long performanceDuration) { SucceededAt = DateTime.UtcNow; Result = result; Latency = latency; PerformanceDuration = performanceDuration; } /// /// Gets a date/time when the current state instance was created. /// [JsonIgnore] public DateTime SucceededAt { get; } /// /// Gets the value returned by a job method. /// public object Result { get; } /// /// Gets the total number of milliseconds passed from a job /// creation time till the start of the performance. /// public long Latency { get; } /// /// Gets the total milliseconds elapsed from a processing start. /// public long PerformanceDuration { get; } /// /// /// Always equals to for the . /// Please see the remarks section of the IState.Name /// article for the details. /// [JsonIgnore] public string Name => StateName; /// public string Reason { get; set; } /// /// /// Always returns for the . /// Please refer to the IState.IsFinal documentation /// for the details. /// [JsonIgnore] public bool IsFinal => true; /// /// /// Always returns for the . /// Please see the description of this property in the /// IState.IgnoreJobLoadException /// article. /// [JsonIgnore] public bool IgnoreJobLoadException => false; /// /// /// Returning dictionary contains the following keys. You can obtain /// the state data by using the /// method. /// /// /// Key /// Type /// Deserialize Method /// Notes /// /// /// SucceededAt /// /// /// Please see the property. /// /// /// PerformanceDuration /// /// /// with /// /// /// Please see the property. /// /// /// Latency /// /// /// with /// /// /// Please see the property. /// /// /// Result /// /// with argument /// /// Please see the property. /// This key may be missing from the dictionary, when the return /// value was . Always check for its existence /// before using it. /// /// /// /// public Dictionary SerializeData() { var data = new Dictionary { { "SucceededAt", JobHelper.SerializeDateTime(SucceededAt) }, { "PerformanceDuration", PerformanceDuration.ToString(CultureInfo.InvariantCulture) }, { "Latency", Latency.ToString(CultureInfo.InvariantCulture) } }; if (Result != null) { string serializedResult; try { serializedResult = SerializationHelper.Serialize(Result, SerializationOption.User); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { serializedResult = "Can not serialize the return value"; } if (serializedResult != null) { data.Add("Result", serializedResult); } } return data; } internal sealed class Handler : IStateHandler { public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction) { transaction.IncrementCounter("stats:succeeded"); } public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction) { transaction.DecrementCounter("stats:succeeded"); } // ReSharper disable once MemberHidesStaticFromOuterClass public string StateName => SucceededState.StateName; } } } ================================================ FILE: src/Hangfire.Core/StatisticsHistoryAttribute.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Globalization; using Hangfire.Common; using Hangfire.States; namespace Hangfire { public sealed class StatisticsHistoryAttribute : JobFilterAttribute, IElectStateFilter { public StatisticsHistoryAttribute() { Order = 30; } public void OnStateElection(ElectStateContext context) { if (context.CandidateState.Name == SucceededState.StateName) { context.Transaction.IncrementCounter( $"stats:succeeded:{DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}", DateTime.UtcNow.AddMonths(1) - DateTime.UtcNow); context.Transaction.IncrementCounter( $"stats:succeeded:{DateTime.UtcNow.ToString("yyyy-MM-dd-HH", CultureInfo.InvariantCulture)}", TimeSpan.FromDays(1)); } else if (context.CandidateState.Name == FailedState.StateName) { context.Transaction.IncrementCounter( $"stats:failed:{DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}", DateTime.UtcNow.AddMonths(1) - DateTime.UtcNow); context.Transaction.IncrementCounter( $"stats:failed:{DateTime.UtcNow.ToString("yyyy-MM-dd-HH", CultureInfo.InvariantCulture)}", TimeSpan.FromDays(1)); } else if (context.CandidateState is DeletedState) { context.Transaction.IncrementCounter( $"stats:deleted:{DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}", DateTime.UtcNow.AddMonths(1) - DateTime.UtcNow); context.Transaction.IncrementCounter( $"stats:deleted:{DateTime.UtcNow.ToString("yyyy-MM-dd-HH", CultureInfo.InvariantCulture)}", TimeSpan.FromDays(1)); } } } } ================================================ FILE: src/Hangfire.Core/Storage/BackgroundServerGoneException.cs ================================================ // This file is part of Hangfire. Copyright © 2018 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; namespace Hangfire.Storage { #if !NETSTANDARD1_3 [Serializable] #endif public class BackgroundServerGoneException : Exception { public BackgroundServerGoneException() { } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected BackgroundServerGoneException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif } } ================================================ FILE: src/Hangfire.Core/Storage/DistributedLockTimeoutException.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; namespace Hangfire.Storage { #if !NETSTANDARD1_3 [Serializable] #endif public class DistributedLockTimeoutException : TimeoutException { public DistributedLockTimeoutException(string resource) : base( $"Timeout expired. The timeout elapsed prior to obtaining a distributed lock on the '{resource}' resource." ) { Resource = resource; } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected DistributedLockTimeoutException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif public string Resource { get; } } } ================================================ FILE: src/Hangfire.Core/Storage/IFetchedJob.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.Storage { public interface IFetchedJob : IDisposable { string JobId { get; } void RemoveFromQueue(); void Requeue(); } } ================================================ FILE: src/Hangfire.Core/Storage/IMonitoringApi.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Storage.Monitoring; namespace Hangfire.Storage { public interface IMonitoringApi { IList Queues(); IList Servers(); JobDetailsDto JobDetails(string jobId); StatisticsDto GetStatistics(); JobList EnqueuedJobs(string queue, int from, int perPage); JobList FetchedJobs(string queue, int from, int perPage); JobList ProcessingJobs(int from, int count); JobList ScheduledJobs(int from, int count); JobList SucceededJobs(int from, int count); JobList FailedJobs(int from, int count); JobList DeletedJobs(int from, int count); long ScheduledCount(); long EnqueuedCount(string queue); long FetchedCount(string queue); long FailedCount(); long ProcessingCount(); long SucceededListCount(); long DeletedListCount(); IDictionary SucceededByDatesCount(); IDictionary FailedByDatesCount(); IDictionary HourlySucceededJobs(); IDictionary HourlyFailedJobs(); } } ================================================ FILE: src/Hangfire.Core/Storage/IStorageConnection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Server; namespace Hangfire.Storage { public interface IStorageConnection : IDisposable { IWriteOnlyTransaction CreateWriteTransaction(); IDisposable AcquireDistributedLock(string resource, TimeSpan timeout); string CreateExpiredJob( Job job, IDictionary parameters, DateTime createdAt, TimeSpan expireIn); IFetchedJob FetchNextJob(string[] queues, CancellationToken cancellationToken); void SetJobParameter(string id, string name, string value); string GetJobParameter(string id, string name); [CanBeNull] JobData GetJobData([NotNull] string jobId); [CanBeNull] StateData GetStateData([NotNull] string jobId); void AnnounceServer(string serverId, ServerContext context); void RemoveServer(string serverId); void Heartbeat(string serverId); int RemoveTimedOutServers(TimeSpan timeOut); // Set operations [NotNull] HashSet GetAllItemsFromSet([NotNull] string key); string GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore); // Hash operations void SetRangeInHash([NotNull] string key, [NotNull] IEnumerable> keyValuePairs); [CanBeNull] Dictionary GetAllEntriesFromHash([NotNull] string key); } } ================================================ FILE: src/Hangfire.Core/Storage/IWriteOnlyTransaction.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.States; namespace Hangfire.Storage { public interface IWriteOnlyTransaction : IDisposable { // Job operations void ExpireJob([NotNull] string jobId, TimeSpan expireIn); void PersistJob([NotNull] string jobId); void SetJobState([NotNull] string jobId, [NotNull] IState state); void AddJobState([NotNull] string jobId, [NotNull] IState state); // Queue operations void AddToQueue([NotNull] string queue, [NotNull] string jobId); // Counter operations void IncrementCounter([NotNull] string key); void IncrementCounter([NotNull] string key, TimeSpan expireIn); void DecrementCounter([NotNull] string key); void DecrementCounter([NotNull] string key, TimeSpan expireIn); // Set operations void AddToSet([NotNull] string key, [NotNull] string value); void AddToSet([NotNull] string key, [NotNull] string value, double score); void RemoveFromSet([NotNull] string key, [NotNull] string value); // List operations void InsertToList([NotNull] string key, [NotNull] string value); void RemoveFromList([NotNull] string key, [NotNull] string value); void TrimList([NotNull] string key, int keepStartingFrom, int keepEndingAt); // Hash operations void SetRangeInHash([NotNull] string key, [NotNull] IEnumerable> keyValuePairs); void RemoveHash([NotNull] string key); void Commit(); } } ================================================ FILE: src/Hangfire.Core/Storage/InvocationData.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; #if !NETSTANDARD1_3 using System.ComponentModel; #endif using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Server; using Newtonsoft.Json; namespace Hangfire.Storage { public class InvocationData { private static readonly ConcurrentDictionary MethodDeserializerCache = new(); private static readonly ConcurrentDictionary MethodSerializerCache = new(); private static readonly ConcurrentDictionary, string>, string[]> ParameterTypesDeserializerCache = new(); [Obsolete("Please use IGlobalConfiguration.UseTypeResolver instead. Will be removed in 2.0.0.")] public static void SetTypeResolver([CanBeNull] Func typeResolver) { TypeHelper.CurrentTypeResolver = typeResolver; } [Obsolete("Please use IGlobalConfiguration.UseTypeSerializer instead. Will be removed in 2.0.0.")] public static void SetTypeSerializer([CanBeNull] Func typeSerializer) { TypeHelper.CurrentTypeSerializer = typeSerializer; } public InvocationData(string type, string method, string parameterTypes, string arguments) : this(type, method, parameterTypes, arguments, null) { } [JsonConstructor] public InvocationData(string type, string method, string parameterTypes, string arguments, string queue) { Type = type; Method = method; ParameterTypes = parameterTypes; Arguments = arguments; Queue = queue; } public string Type { get; } public string Method { get; } public string ParameterTypes { get; } public string Arguments { get; set; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Queue { get; } [Obsolete("Please use DeserializeJob() method instead. Will be removed in 2.0.0 for clarity.")] public Job Deserialize() { return DeserializeJob(); } [Obsolete("Please use SerializeJob(Job) method instead. Will be removed in 2.0.0 for clarity.")] public static InvocationData Serialize(Job job) { return SerializeJob(job); } public Job DeserializeJob() { var typeResolver = TypeHelper.CurrentTypeResolver; try { CachedDeserializeMethod(typeResolver, Type, Method, ParameterTypes, out var type, out var method); object[] arguments; if (Arguments != null && !Arguments.Equals("[]", StringComparison.Ordinal)) { var argumentsArray = SerializationHelper.Deserialize(Arguments); arguments = DeserializeArguments(method, argumentsArray); } else { arguments = []; } return new Job(type, method, arguments, Queue); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { throw new JobLoadException("Could not load the job. See inner exception for the details.", ex); } } public static InvocationData SerializeJob(Job job) { CachedSerializeMethod( TypeHelper.CurrentTypeSerializer, job.Type, job.Method, out var typeName, out var methodName, out var parameterTypes); var arguments = job.Args.Count == 0 ? "[]" : SerializationHelper.Serialize(SerializeArguments(job.Method, job.Args)); return new InvocationData(typeName, methodName, parameterTypes, arguments, job.Queue); } public static InvocationData DeserializePayload(string payload) { if (payload == null) throw new ArgumentNullException(nameof(payload)); JobPayload jobPayload = null; Exception exception = null; try { jobPayload = SerializationHelper.Deserialize(payload); if (jobPayload == null) { throw new InvalidOperationException("Deserialize returned `null` for a non-null payload."); } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { exception = ex; } if (exception == null && jobPayload.TypeName != null && jobPayload.MethodName != null) { return new InvocationData( jobPayload.TypeName, jobPayload.MethodName, SerializationHelper.Serialize(jobPayload.ParameterTypes), SerializationHelper.Serialize(jobPayload.Arguments), jobPayload.Queue); } var data = SerializationHelper.Deserialize(payload); if (data == null) { throw new InvalidOperationException("Deserialize returned `null` for a non-null payload."); } if (data.Type == null || data.Method == null) { data = SerializationHelper.Deserialize(payload, SerializationOption.User); } return data; } public string SerializePayload(bool excludeArguments = false) { if (GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_170)) { var parameterTypes = DeserializeParameterTypesArray(TypeHelper.CurrentTypeSerializer, ParameterTypes); var arguments = excludeArguments ? null : SerializationHelper.Deserialize(Arguments); return SerializationHelper.Serialize(new JobPayload { TypeName = Type, MethodName = Method, ParameterTypes = parameterTypes?.Length > 0 ? parameterTypes : null, Arguments = arguments?.Length > 0 ? arguments : null, Queue = Queue }); } return SerializationHelper.Serialize(excludeArguments ? new InvocationData(Type, Method, ParameterTypes, null, Queue) : this); } private static string[] DeserializeParameterTypesArray(Func typeSerializer, string parameterTypes) { return ParameterTypesDeserializerCache.GetOrAdd(Tuple.Create(typeSerializer, parameterTypes), static tuple => { try { return SerializationHelper.Deserialize(tuple.Item2); } catch (Exception outerException) when (outerException.IsCatchableExceptionType()) { try { var types = SerializationHelper.Deserialize(tuple.Item2); return types.Select(tuple.Item1).ToArray(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ExceptionDispatchInfo.Capture(outerException).Throw(); throw; } } }); } internal static string[] SerializeArguments(MethodInfo methodInfo, IReadOnlyList arguments) { var serializedArguments = new List(arguments.Count); var parameters = methodInfo.GetParameters(); for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; var argument = arguments[i]; string value; if (argument != null) { if (!GlobalConfiguration.HasCompatibilityLevel(CompatibilityLevel.Version_170) && argument is DateTime dateTime) { value = dateTime.ToString("o", CultureInfo.InvariantCulture); } else if (argument is CancellationToken) { // CancellationToken type instances are substituted with ShutdownToken // during the background job performance, so we don't need to store // their values. value = null; } else { value = SerializationHelper.Serialize( argument, parameter.ParameterType, SerializationOption.User); } } else { value = null; } // Logic, related to optional parameters and their default values, // can be skipped, because it is impossible to omit them in // lambda-expressions (leads to a compile-time error). serializedArguments.Add(value); } return serializedArguments.ToArray(); } internal static object[] DeserializeArguments(MethodInfo methodInfo, string[] arguments) { if (arguments == null) return []; var parameters = methodInfo.GetParameters(); var result = new List(arguments.Length); for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; var argument = arguments[i]; object value; if (CoreBackgroundJobPerformer.Substitutions.ContainsKey(parameter.ParameterType)) { value = parameter.ParameterType.GetTypeInfo().IsValueType ? Activator.CreateInstance(parameter.ParameterType) : null; } else { value = DeserializeArgument(argument, parameter.ParameterType); } result.Add(value); } return result.ToArray(); } private static void CachedSerializeMethod( Func typeSerializer, Type type, MethodInfo methodInfo, out string typeName, out string methodName, out string parameterTypes) { var entry = MethodSerializerCache.GetOrAdd( new MethodSerializerCacheKey { TypeSerializer = typeSerializer, Type = type, Method = methodInfo }, static key => { return new MethodSerializerCacheValue { TypeName = key.TypeSerializer(key.Type), MethodName = key.Method.Name, ParameterTypes = SerializationHelper.Serialize( key.Method.GetParameters().Select(x => key.TypeSerializer(x.ParameterType)).ToArray()) }; }); typeName = entry.TypeName; methodName = entry.MethodName; parameterTypes = entry.ParameterTypes; } private static void CachedDeserializeMethod( Func typeResolver, string typeName, string methodName, string parameterTypes, out Type type, out MethodInfo methodInfo) { var entry = MethodDeserializerCache.GetOrAdd( new MethodDeserializerCacheKey { TypeResolver = typeResolver, TypeName = typeName, MethodName = methodName, ParameterTypes = parameterTypes }, static key => { var type = key.TypeResolver(key.TypeName); var parameterTypesArray = DeserializeParameterTypesArray(TypeHelper.CurrentTypeSerializer, key.ParameterTypes); var parameterTypes = parameterTypesArray?.Select(key.TypeResolver).ToArray(); var method = type.GetNonOpenMatchingMethod(key.MethodName, parameterTypes); if (method == null) { var parametersString = parameterTypes != null ? String.Join(", ", parameterTypes.Select(static x => x.Name)) : key.ParameterTypes ?? String.Empty; throw new InvalidOperationException( $"The type `{type.FullName}` does not contain a method with signature `{key.MethodName}({parametersString})`"); } return new MethodDeserializerCacheValue { Type = type, Method = method }; }); type = entry.Type; methodInfo = entry.Method; } private static object DeserializeArgument(string argument, Type type) { object value; try { value = SerializationHelper.Deserialize(argument, type, SerializationOption.User); } catch (Exception jsonException) when (jsonException.IsCatchableExceptionType()) { if (type == typeof(object)) { // Special case for handling object types, because string can not // be converted to object type. value = argument; } else if ((type == typeof(DateTime) || type == typeof(DateTime?)) && ParseDateTimeArgument(argument, out var dateTime)) { value = dateTime; } else { #if !NETSTANDARD1_3 try { var converter = TypeDescriptor.GetConverter(type); // ReferenceConverter can't correctly convert the serialized // data. This may happen when FromJson method threw an exception, // we should rethrow it instead of trying to deserialize. if (converter.GetType() == typeof(ReferenceConverter)) { ExceptionDispatchInfo.Capture(jsonException).Throw(); throw; } value = converter.ConvertFromInvariantString(argument); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ExceptionDispatchInfo.Capture(jsonException).Throw(); throw; } #else throw; #endif } } return value; } internal static bool ParseDateTimeArgument(string argument, out DateTime value) { var result = DateTime.TryParseExact( argument, "MM/dd/yyyy HH:mm:ss.ffff", CultureInfo.CurrentCulture, DateTimeStyles.None, out var dateTime); if (!result) { result = DateTime.TryParse( argument, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTime); } value = dateTime; return result; } private sealed class JobPayload { [JsonProperty("t")] public string TypeName { get; set; } [JsonProperty("m")] public string MethodName { get; set; } [JsonProperty("p", NullValueHandling = NullValueHandling.Ignore)] public string[] ParameterTypes { get; set; } [JsonProperty("a", NullValueHandling = NullValueHandling.Ignore)] public string[] Arguments { get; set; } [JsonProperty("q", NullValueHandling = NullValueHandling.Ignore)] public string Queue { get; set; } } private readonly record struct MethodDeserializerCacheKey { public Func TypeResolver { get; init; } public string TypeName { get; init; } public string MethodName { get; init; } public string ParameterTypes { get; init; } } private readonly record struct MethodDeserializerCacheValue { public Type Type { get; init; } public MethodInfo Method { get; init; } } private readonly record struct MethodSerializerCacheKey { public Func TypeSerializer { get; init; } public Type Type { get; init; } public MethodInfo Method { get; init; } } private readonly record struct MethodSerializerCacheValue { public string TypeName { get; init; } public string MethodName { get; init; } public string ParameterTypes { get; init; } } } } ================================================ FILE: src/Hangfire.Core/Storage/JobData.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage { public class JobData { public string State { get; set; } public Job Job { get; set; } public InvocationData InvocationData { get; set; } public DateTime CreatedAt { get; set; } public IReadOnlyDictionary ParametersSnapshot { get; set; } public JobLoadException LoadException { get; set; } public void EnsureLoaded() { if (LoadException != null) { throw LoadException; } } } } ================================================ FILE: src/Hangfire.Core/Storage/JobStorageConnection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Server; namespace Hangfire.Storage { public abstract class JobStorageConnection : IStorageConnection { public virtual void Dispose() { GC.SuppressFinalize(this); } // Common public abstract IWriteOnlyTransaction CreateWriteTransaction(); public abstract IDisposable AcquireDistributedLock(string resource, TimeSpan timeout); // Background jobs public abstract string CreateExpiredJob(Job job, IDictionary parameters, DateTime createdAt, TimeSpan expireIn); public abstract IFetchedJob FetchNextJob(string[] queues, CancellationToken cancellationToken); public abstract void SetJobParameter(string id, string name, string value); public abstract string GetJobParameter(string id, string name); public abstract JobData GetJobData(string jobId); public abstract StateData GetStateData(string jobId); // Servers public abstract void AnnounceServer(string serverId, ServerContext context); public abstract void RemoveServer(string serverId); public abstract void Heartbeat(string serverId); public abstract int RemoveTimedOutServers(TimeSpan timeOut); // Sets public abstract HashSet GetAllItemsFromSet(string key); public abstract string GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore); public virtual List GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore, int count) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Connection.BatchedGetFirstByLowest); } public virtual long GetSetCount([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual long GetSetCount([NotNull] IEnumerable keys, int limit) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Connection.LimitedGetSetCount); } public virtual bool GetSetContains([NotNull] string key, [NotNull] string value) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Connection.GetSetContains); } public virtual List GetRangeFromSet([NotNull] string key, int startingFrom, int endingAt) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual TimeSpan GetSetTtl([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } // Hashes public abstract void SetRangeInHash(string key, IEnumerable> keyValuePairs); public abstract Dictionary GetAllEntriesFromHash(string key); public virtual string GetValueFromHash([NotNull] string key, [NotNull] string name) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual long GetHashCount([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual TimeSpan GetHashTtl([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } // Lists public virtual long GetListCount([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual List GetAllItemsFromList([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual List GetRangeFromList([NotNull] string key, int startingFrom, int endingAt) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual TimeSpan GetListTtl([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } // Counters public virtual long GetCounter([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual DateTime GetUtcDateTime() { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Connection.GetUtcDateTime); } } } ================================================ FILE: src/Hangfire.Core/Storage/JobStorageFeatures.cs ================================================ // This file is part of Hangfire. // Copyright © 2023 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using Hangfire.Annotations; namespace Hangfire.Storage { public static class JobStorageFeatures { internal static readonly string TransactionalAcknowledgePrefix = "TransactionalAcknowledge:"; public static readonly string JobQueueProperty = "Job.Queue"; public static readonly string ExtendedApi = "Storage.ExtendedApi"; public static readonly string ProcessesInsteadOfComponents = "Storage.ProcessesInsteadOfComponents"; public static class Connection { public static readonly string GetUtcDateTime = "Connection.GetUtcDateTime"; public static readonly string GetSetContains = "Connection.GetSetContains"; public static readonly string LimitedGetSetCount = "Connection.GetSetCount.Limited"; public static readonly string BatchedGetFirstByLowest = "Connection.BatchedGetFirstByLowestScoreFromSet"; } public static class Transaction { public static readonly string AcquireDistributedLock = "Transaction.AcquireDistributedLock"; public static readonly string CreateJob = "Transaction.CreateJob"; public static readonly string SetJobParameter = "Transaction.SetJobParameter"; private static readonly ConcurrentDictionary RemoveFromQueueFeatureCache = new(); public static string RemoveFromQueue(Type fetchedJobType) { return RemoveFromQueueFeatureCache.GetOrAdd( fetchedJobType, static type => TransactionalAcknowledgePrefix + type.Name); } } public static class Monitoring { public static readonly string DeletedStateGraphs = "Monitoring.DeletedStateGraphs"; public static readonly string AwaitingJobs = "Monitoring.AwaitingJobs"; } public static Exception GetNotSupportedException([NotNull] string featureId) { if (featureId == null) throw new ArgumentNullException(nameof(featureId)); return new NotSupportedException( $"Current storage implementation does not support the '{featureId}' feature."); } } } ================================================ FILE: src/Hangfire.Core/Storage/JobStorageMonitor.cs ================================================ // This file is part of Hangfire. // Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Storage.Monitoring; namespace Hangfire.Storage { public abstract class JobStorageMonitor : IMonitoringApi { public abstract IList Queues(); public abstract IList Servers(); public abstract JobDetailsDto JobDetails(string jobId); public abstract StatisticsDto GetStatistics(); public abstract JobList EnqueuedJobs(string queue, int from, int perPage); public abstract JobList FetchedJobs(string queue, int from, int perPage); public abstract JobList ProcessingJobs(int from, int count); public abstract JobList ScheduledJobs(int from, int count); public abstract JobList SucceededJobs(int from, int count); public abstract JobList FailedJobs(int from, int count); public abstract JobList DeletedJobs(int from, int count); public virtual JobList AwaitingJobs(int from, int count) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Monitoring.AwaitingJobs); } public abstract long ScheduledCount(); public abstract long EnqueuedCount(string queue); public abstract long FetchedCount(string queue); public abstract long FailedCount(); public abstract long ProcessingCount(); public abstract long SucceededListCount(); public abstract long DeletedListCount(); public virtual long AwaitingCount() { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Monitoring.AwaitingJobs); } public abstract IDictionary SucceededByDatesCount(); public abstract IDictionary FailedByDatesCount(); public virtual IDictionary DeletedByDatesCount() { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Monitoring.DeletedStateGraphs); } public abstract IDictionary HourlySucceededJobs(); public abstract IDictionary HourlyFailedJobs(); public virtual IDictionary HourlyDeletedJobs() { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Monitoring.DeletedStateGraphs); } } } ================================================ FILE: src/Hangfire.Core/Storage/JobStorageTransaction.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.States; namespace Hangfire.Storage { public abstract class JobStorageTransaction : IWriteOnlyTransaction { public virtual void Dispose() { GC.SuppressFinalize(this); } public abstract void ExpireJob(string jobId, TimeSpan expireIn); public abstract void PersistJob(string jobId); public abstract void SetJobState(string jobId, IState state); public abstract void AddJobState(string jobId, IState state); public abstract void AddToQueue(string queue, string jobId); public abstract void IncrementCounter(string key); public abstract void IncrementCounter(string key, TimeSpan expireIn); public abstract void DecrementCounter(string key); public abstract void DecrementCounter(string key, TimeSpan expireIn); public abstract void AddToSet(string key, string value); public abstract void AddToSet(string key, string value, double score); public abstract void RemoveFromSet(string key, string value); public abstract void InsertToList(string key, string value); public abstract void RemoveFromList(string key, string value); public abstract void TrimList(string key, int keepStartingFrom, int keepEndingAt); public abstract void SetRangeInHash(string key, IEnumerable> keyValuePairs); public abstract void RemoveHash(string key); public abstract void Commit(); public virtual void ExpireSet([NotNull] string key, TimeSpan expireIn) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void ExpireList([NotNull] string key, TimeSpan expireIn) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void ExpireHash([NotNull] string key, TimeSpan expireIn) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void PersistSet([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void PersistList([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void PersistHash([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void AddRangeToSet([NotNull] string key, [NotNull] IList items) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void RemoveSet([NotNull] string key) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.ExtendedApi); } public virtual void AcquireDistributedLock([NotNull] string resource, TimeSpan timeout) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Transaction.AcquireDistributedLock); } public virtual void RemoveFromQueue([NotNull] IFetchedJob fetchedJob) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Transaction.RemoveFromQueue(fetchedJob.GetType())); } public virtual void SetJobParameter([NotNull] string jobId, [NotNull] string name, [CanBeNull] string value) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Transaction.SetJobParameter); } public virtual string CreateJob([NotNull] Job job, [NotNull] IDictionary parameters, DateTime createdAt, TimeSpan expireIn) { throw JobStorageFeatures.GetNotSupportedException(JobStorageFeatures.Transaction.CreateJob); } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/AwaitingJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2022 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class AwaitingJobDto { public AwaitingJobDto() { InAwaitingState = true; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public DateTime? AwaitingAt { get; set; } public bool InAwaitingState { get; set; } public IDictionary StateData { get; set; } public string ParentStateName { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/DeletedJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class DeletedJobDto { public DeletedJobDto() { InDeletedState = true; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public DateTime? DeletedAt { get; set; } public bool InDeletedState { get; set; } public IDictionary StateData { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/EnqueuedJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class EnqueuedJobDto { public EnqueuedJobDto() { InEnqueuedState = true; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public string State { get; set; } public DateTime? EnqueuedAt { get; set; } public bool InEnqueuedState { get; set; } public IDictionary StateData { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/FailedJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class FailedJobDto { public FailedJobDto() { InFailedState = true; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public string Reason { get; set; } public DateTime? FailedAt { get; set; } public string ExceptionType { get; set; } public string ExceptionMessage { get; set; } public string ExceptionDetails { get; set; } public bool InFailedState { get; set; } public IDictionary StateData { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/FetchedJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class FetchedJobDto { public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public string State { get; set; } public DateTime? FetchedAt { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/JobDetailsDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class JobDetailsDto { public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public DateTime? CreatedAt { get; set; } public IDictionary Properties { get; set; } public IList History { get; set; } public DateTime? ExpireAt { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/JobList.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; namespace Hangfire.Storage.Monitoring { public class JobList : List> { public JobList(IEnumerable> source) : base(source) { } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/ProcessingJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class ProcessingJobDto { public ProcessingJobDto() { InProcessingState = true; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public bool InProcessingState { get; set; } public string ServerId { get; set; } public DateTime? StartedAt { get; set; } public IDictionary StateData { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/QueueWithTopEnqueuedJobsDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Storage.Monitoring { public class QueueWithTopEnqueuedJobsDto { public string Name { get; set; } public long Length { get; set; } public long? Fetched { get; set; } public JobList FirstJobs { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/ScheduledJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class ScheduledJobDto { public ScheduledJobDto() { InScheduledState = true; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public DateTime EnqueueAt { get; set; } public DateTime? ScheduledAt { get; set; } public bool InScheduledState { get; set; } public IDictionary StateData { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/ServerDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; namespace Hangfire.Storage.Monitoring { public class ServerDto { public string Name { get; set; } public int WorkersCount { get; set; } public DateTime StartedAt { get; set; } public IList Queues { get; set; } public DateTime? Heartbeat { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/StateHistoryDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; namespace Hangfire.Storage.Monitoring { public class StateHistoryDto { public string StateName { get; set; } public string Reason { get; set; } public DateTime CreatedAt { get; set; } public IDictionary Data { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/StatisticsDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.Storage.Monitoring { public class StatisticsDto { public long Servers { get; set; } public long Recurring { get; set; } public long Enqueued { get; set; } public long Queues { get; set; } public long Scheduled { get; set; } public long Processing { get; set; } public long Succeeded { get; set; } public long Failed { get; set; } public long Deleted { get; set; } public long? Retries { get; set; } public long? Awaiting { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/Monitoring/SucceededJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Common; namespace Hangfire.Storage.Monitoring { public class SucceededJobDto { public SucceededJobDto() { InSucceededState = true; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public InvocationData InvocationData { get; set; } public object Result { get; set; } public long? TotalDuration { get; set; } public DateTime? SucceededAt { get; set; } public bool InSucceededState { get; set; } public IDictionary StateData { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/RecurringJobDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Common; namespace Hangfire.Storage { public class RecurringJobDto { public string Id { get; set; } public string Cron { get; set; } public string Queue { get; set; } public Job Job { get; set; } public JobLoadException LoadException { get; set; } public DateTime? NextExecution { get; set; } public string LastJobId { get; set; } public string LastJobState { get; set; } public DateTime? LastExecution { get; set; } public DateTime? CreatedAt { get; set; } public bool Removed { get; set; } public string TimeZoneId { get; set; } public string Error { get; set; } public int RetryAttempt { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/StateData.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; using Hangfire.Annotations; // ReSharper disable NotNullMemberIsNotInitialized - Let's trust them! namespace Hangfire.Storage { public class StateData { [NotNull] public string Name { get; set; } [CanBeNull] public string Reason { get; set; } [NotNull] public IDictionary Data { get; set; } } } ================================================ FILE: src/Hangfire.Core/Storage/StorageConnectionExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.Common; namespace Hangfire.Storage { public static class StorageConnectionExtensions { public static IDisposable AcquireDistributedJobLock( [NotNull] this IStorageConnection connection, [NotNull] string jobId, TimeSpan timeout) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (jobId == null) throw new ArgumentNullException(nameof(jobId)); return connection.AcquireDistributedLock( $"job:{jobId}:state-lock", timeout); } public static void AcquireDistributedJobLock( [NotNull] this JobStorageTransaction transaction, [NotNull] string jobId, TimeSpan timeout) { if (transaction == null) throw new ArgumentNullException(nameof(transaction)); if (jobId == null) throw new ArgumentNullException(nameof(jobId)); transaction.AcquireDistributedLock($"job:{jobId}:state-lock", timeout); } public static long GetRecurringJobCount([NotNull] this JobStorageConnection connection) { if (connection == null) throw new ArgumentNullException(nameof(connection)); return connection.GetSetCount("recurring-jobs"); } public static List GetRecurringJobIds( [NotNull] this JobStorageConnection connection, int startingFrom, int endingAt) { if (connection == null) throw new ArgumentNullException(nameof(connection)); return connection.GetRangeFromSet("recurring-jobs", startingFrom, endingAt); } public static List GetRecurringJobs( [NotNull] this JobStorageConnection connection, int startingFrom, int endingAt) { if (connection == null) throw new ArgumentNullException(nameof(connection)); var ids = connection.GetRecurringJobIds(startingFrom, endingAt); return GetRecurringJobDtos(connection, ids); } public static List GetRecurringJobs([NotNull] this IStorageConnection connection) { if (connection == null) throw new ArgumentNullException(nameof(connection)); var ids = connection.GetAllItemsFromSet("recurring-jobs"); return GetRecurringJobDtos(connection, ids); } public static List GetRecurringJobs([NotNull] this IStorageConnection connection, IEnumerable ids) { if (connection == null) throw new ArgumentNullException(nameof(connection)); return GetRecurringJobDtos(connection, ids); } private static List GetRecurringJobDtos(IStorageConnection connection, IEnumerable ids) { var result = new List(); foreach (var id in ids) { var hash = connection.GetAllEntriesFromHash($"recurring-job:{id}"); // TODO: Remove this in 2.0 (breaking change) if (hash == null) { result.Add(new RecurringJobDto { Id = id, Removed = true }); continue; } var dto = new RecurringJobDto { Id = id }; if (hash.TryGetValue("Cron", out var cron) && !String.IsNullOrWhiteSpace(cron)) { dto.Cron = cron; } try { if (hash.TryGetValue("Job", out var payload) && !String.IsNullOrWhiteSpace(payload)) { var invocationData = InvocationData.DeserializePayload(payload); dto.Job = invocationData.DeserializeJob(); } } catch (JobLoadException ex) { dto.LoadException = ex; } if (hash.TryGetValue("NextExecution", out var nextExecution)) { dto.NextExecution = JobHelper.DeserializeNullableDateTime(nextExecution); } if (hash.TryGetValue("LastJobId", out var lastJobId) && !string.IsNullOrWhiteSpace(lastJobId)) { dto.LastJobId = lastJobId; var stateData = connection.GetStateData(dto.LastJobId); if (stateData != null) { dto.LastJobState = stateData.Name; } } if (hash.TryGetValue("Queue", out var queue)) { dto.Queue = queue; } if (hash.TryGetValue("LastExecution", out var lastExecution)) { dto.LastExecution = JobHelper.DeserializeNullableDateTime(lastExecution); } if (hash.TryGetValue("TimeZoneId", out var timeZoneId)) { dto.TimeZoneId = timeZoneId; } if (hash.TryGetValue("CreatedAt", out var createdAt)) { dto.CreatedAt = JobHelper.DeserializeNullableDateTime(createdAt); } if (hash.TryGetValue("Error", out var error) && !String.IsNullOrEmpty(error)) { dto.Error = error; } if (hash.TryGetValue("RetryAttempt", out var attemptString) && Int32.TryParse(attemptString, out var retryAttempt)) { dto.RetryAttempt = retryAttempt; } else { dto.RetryAttempt = 0; } result.Add(dto); } return result; } } } ================================================ FILE: src/Hangfire.Core/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.1": { "CronExpressionDescriptor": { "type": "Direct", "requested": "[1.21.0, )", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Direct", "requested": "[0.11.1, )", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "LibLog": { "type": "Direct", "requested": "[1.5.0, )", "resolved": "1.5.0", "contentHash": "0bNoz50GPHYnS3fv/qTS2dFBeYgoppM1t//gWULGxY/TM5TNxxknUWqOqzE8eZgLIa2ENfIwzumvbeWFnx14rg==" }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net451": "1.0.3" } }, "Microsoft.Owin": { "type": "Direct", "requested": "[4.2.3, )", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "MoreLinq.Source.MoreEnumerable.Pairwise": { "type": "Direct", "requested": "[1.0.1, )", "resolved": "1.0.1", "contentHash": "N1bmYqxItSwixZ72KJ/V4e3mEnIO5AtMcmjtilqe+Ulp4GpSje9DP+1Cv4j01s3uG4h7P4G1axUEODL1yWB5Og==" }, "Newtonsoft.Json": { "type": "Direct", "requested": "[5.0.1, )", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "StackTraceFormatter.Source": { "type": "Direct", "requested": "[1.1.0, )", "resolved": "1.1.0", "contentHash": "69Zxg50Sm1kq9hvxPgEo0w9li8oiD2qVT87GGsqXJP2vfYs33LVKGHRMSP2Z4iTP3UC0PJTeurCeWY2t7X/76Q==", "dependencies": { "MoreLinq.Source.MoreEnumerable.Pairwise": "[1.0.1, 2.0.0)", "StackTraceParser.Source": "[1.2.0, 2.0.0)" } }, "StackTraceParser.Source": { "type": "Direct", "requested": "[1.3.0, )", "resolved": "1.3.0", "contentHash": "tOdf1XpHE2YQJpBERplZA2kMTJQtJnKzSc7GMO/yeNNGuXNueVqySfIr/gfU1vx9k2FO8u0zD4mtnZGwKQM+Zg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.NETFramework.ReferenceAssemblies.net451": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "vVPinxdLrwoX81ApbNIHDBI6qymQEy8eSOxDNBgKJtc2+cifnF0oT1U2d3EFx+V5O68yaqna2myZJNsgKCpVkA==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" } }, ".NETFramework,Version=v4.6": { "CronExpressionDescriptor": { "type": "Direct", "requested": "[1.21.0, )", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Direct", "requested": "[0.11.1, )", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "LibLog": { "type": "Direct", "requested": "[1.5.0, )", "resolved": "1.5.0", "contentHash": "0bNoz50GPHYnS3fv/qTS2dFBeYgoppM1t//gWULGxY/TM5TNxxknUWqOqzE8eZgLIa2ENfIwzumvbeWFnx14rg==" }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net46": "1.0.3" } }, "Microsoft.Owin": { "type": "Direct", "requested": "[4.2.3, )", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "MoreLinq.Source.MoreEnumerable.Pairwise": { "type": "Direct", "requested": "[1.0.1, )", "resolved": "1.0.1", "contentHash": "N1bmYqxItSwixZ72KJ/V4e3mEnIO5AtMcmjtilqe+Ulp4GpSje9DP+1Cv4j01s3uG4h7P4G1axUEODL1yWB5Og==" }, "Newtonsoft.Json": { "type": "Direct", "requested": "[5.0.1, )", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "StackTraceFormatter.Source": { "type": "Direct", "requested": "[1.1.0, )", "resolved": "1.1.0", "contentHash": "69Zxg50Sm1kq9hvxPgEo0w9li8oiD2qVT87GGsqXJP2vfYs33LVKGHRMSP2Z4iTP3UC0PJTeurCeWY2t7X/76Q==", "dependencies": { "MoreLinq.Source.MoreEnumerable.Pairwise": "[1.0.1, 2.0.0)", "StackTraceParser.Source": "[1.2.0, 2.0.0)" } }, "StackTraceParser.Source": { "type": "Direct", "requested": "[1.3.0, )", "resolved": "1.3.0", "contentHash": "tOdf1XpHE2YQJpBERplZA2kMTJQtJnKzSc7GMO/yeNNGuXNueVqySfIr/gfU1vx9k2FO8u0zD4mtnZGwKQM+Zg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.NETFramework.ReferenceAssemblies.net46": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "NiiK1wNEKbDFxF08ybLkjwEn8g6SuIrUl/Y+jlAMcf3NsTtlU0JFRWFPJENV9yyZkW6nBxk/pniBlyZtE5fHwQ==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" } }, ".NETStandard,Version=v1.3": { "Cronos": { "type": "Direct", "requested": "[0.11.1, )", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "LibLog": { "type": "Direct", "requested": "[1.5.0, )", "resolved": "1.5.0", "contentHash": "0bNoz50GPHYnS3fv/qTS2dFBeYgoppM1t//gWULGxY/TM5TNxxknUWqOqzE8eZgLIa2ENfIwzumvbeWFnx14rg==" }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "MoreLinq.Source.MoreEnumerable.Pairwise": { "type": "Direct", "requested": "[1.0.1, )", "resolved": "1.0.1", "contentHash": "N1bmYqxItSwixZ72KJ/V4e3mEnIO5AtMcmjtilqe+Ulp4GpSje9DP+1Cv4j01s3uG4h7P4G1axUEODL1yWB5Og==" }, "NETStandard.Library": { "type": "Direct", "requested": "[1.6.1, )", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[9.0.1, )", "resolved": "9.0.1", "contentHash": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", "dependencies": { "Microsoft.CSharp": "4.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Serialization.Primitives": "4.1.1", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11" } }, "StackTraceFormatter.Source": { "type": "Direct", "requested": "[1.1.0, )", "resolved": "1.1.0", "contentHash": "69Zxg50Sm1kq9hvxPgEo0w9li8oiD2qVT87GGsqXJP2vfYs33LVKGHRMSP2Z4iTP3UC0PJTeurCeWY2t7X/76Q==", "dependencies": { "MoreLinq.Source.MoreEnumerable.Pairwise": "[1.0.1, 2.0.0)", "StackTraceParser.Source": "[1.2.0, 2.0.0)" } }, "StackTraceParser.Source": { "type": "Direct", "requested": "[1.3.0, )", "resolved": "1.3.0", "contentHash": "tOdf1XpHE2YQJpBERplZA2kMTJQtJnKzSc7GMO/yeNNGuXNueVqySfIr/gfU1vx9k2FO8u0zD4mtnZGwKQM+Zg==" }, "System.Threading.Thread": { "type": "Direct", "requested": "[4.0.0, )", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Threading.ThreadPool": { "type": "Direct", "requested": "[4.0.10, )", "resolved": "4.0.10", "contentHash": "IMXgB5Vf/5Qw1kpoVgJMOvUO1l32aC+qC3OaIZjWJOjvcxuxNWOK2ZTWWYXfij22NHxT2j1yWX5vlAeQWld9vA==", "dependencies": { "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "17h8b5mXa87XYKrrVqdgZ38JefSUqLChUQpXgSnpzsM0nDOhE40FTeNWOJ/YmySGV6tG6T8+hjz6vxbknHJr6A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Threading": "4.0.11" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Runtime.Serialization.Primitives": { "type": "Transitive", "resolved": "4.1.1", "contentHash": "HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==", "dependencies": { "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } } }, ".NETStandard,Version=v2.0": { "Cronos": { "type": "Direct", "requested": "[0.11.1, )", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "LibLog": { "type": "Direct", "requested": "[1.5.0, )", "resolved": "1.5.0", "contentHash": "0bNoz50GPHYnS3fv/qTS2dFBeYgoppM1t//gWULGxY/TM5TNxxknUWqOqzE8eZgLIa2ENfIwzumvbeWFnx14rg==" }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.CSharp": { "type": "Direct", "requested": "[4.4.0, )", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "MoreLinq.Source.MoreEnumerable.Pairwise": { "type": "Direct", "requested": "[1.0.1, )", "resolved": "1.0.1", "contentHash": "N1bmYqxItSwixZ72KJ/V4e3mEnIO5AtMcmjtilqe+Ulp4GpSje9DP+1Cv4j01s3uG4h7P4G1axUEODL1yWB5Og==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[11.0.1, )", "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, "StackTraceFormatter.Source": { "type": "Direct", "requested": "[1.1.0, )", "resolved": "1.1.0", "contentHash": "69Zxg50Sm1kq9hvxPgEo0w9li8oiD2qVT87GGsqXJP2vfYs33LVKGHRMSP2Z4iTP3UC0PJTeurCeWY2t7X/76Q==", "dependencies": { "MoreLinq.Source.MoreEnumerable.Pairwise": "[1.0.1, 2.0.0)", "StackTraceParser.Source": "[1.2.0, 2.0.0)" } }, "StackTraceParser.Source": { "type": "Direct", "requested": "[1.3.0, )", "resolved": "1.3.0", "contentHash": "tOdf1XpHE2YQJpBERplZA2kMTJQtJnKzSc7GMO/yeNNGuXNueVqySfIr/gfU1vx9k2FO8u0zD4mtnZGwKQM+Zg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" } } } } ================================================ FILE: src/Hangfire.NetCore/AspNetCore/AspNetCoreJobActivator.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Microsoft.Extensions.DependencyInjection; namespace Hangfire.AspNetCore { public class AspNetCoreJobActivator : JobActivator { private readonly IServiceScopeFactory _serviceScopeFactory; public AspNetCoreJobActivator([NotNull] IServiceScopeFactory serviceScopeFactory) { if (serviceScopeFactory == null) throw new ArgumentNullException(nameof(serviceScopeFactory)); _serviceScopeFactory = serviceScopeFactory; } public override JobActivatorScope BeginScope(JobActivatorContext context) { return new AspNetCoreJobActivatorScope(_serviceScopeFactory.CreateScope()); } #pragma warning disable CS0672 // Member overrides obsolete member public override JobActivatorScope BeginScope() #pragma warning restore CS0672 // Member overrides obsolete member { return new AspNetCoreJobActivatorScope(_serviceScopeFactory.CreateScope()); } } } ================================================ FILE: src/Hangfire.NetCore/AspNetCore/AspNetCoreJobActivatorScope.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Microsoft.Extensions.DependencyInjection; namespace Hangfire.AspNetCore { internal sealed class AspNetCoreJobActivatorScope : JobActivatorScope { private readonly IServiceScope _serviceScope; public AspNetCoreJobActivatorScope([NotNull] IServiceScope serviceScope) { if (serviceScope == null) throw new ArgumentNullException(nameof(serviceScope)); _serviceScope = serviceScope; } public override object Resolve(Type type) { return ActivatorUtilities.GetServiceOrCreateInstance(_serviceScope.ServiceProvider, type); } public override void DisposeScope() { #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1 if (_serviceScope is IAsyncDisposable asyncDisposable) { // Service scope disposal is triggered inside a dedicated background thread, // while Task result is being set in CLR's Thread Pool, so no deadlocks on // wait should happen. #pragma warning disable CA2012 asyncDisposable.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); #pragma warning restore CA2012 return; } #endif _serviceScope.Dispose(); } } } ================================================ FILE: src/Hangfire.NetCore/AspNetCore/AspNetCoreLog.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Logging; using Microsoft.Extensions.Logging; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Hangfire.AspNetCore { internal sealed class AspNetCoreLog : ILog { private static readonly Func MessageFormatterFunc = MessageFormatter; private readonly ILogger _targetLogger; public AspNetCoreLog([NotNull] ILogger targetLogger) { if (targetLogger == null) throw new ArgumentNullException(nameof(targetLogger)); _targetLogger = targetLogger; } public bool Log(Logging.LogLevel logLevel, Func messageFunc, Exception exception = null) { var targetLogLevel = ToTargetLogLevel(logLevel); // When messageFunc is null, Hangfire.Logging // just determines is logging enabled. if (messageFunc == null) { return _targetLogger.IsEnabled(targetLogLevel); } _targetLogger.Log(targetLogLevel, 0, messageFunc(), exception, MessageFormatterFunc); return true; } private static LogLevel ToTargetLogLevel(Logging.LogLevel logLevel) { switch (logLevel) { case Logging.LogLevel.Trace: return LogLevel.Trace; case Logging.LogLevel.Debug: return LogLevel.Debug; case Logging.LogLevel.Info: return LogLevel.Information; case Logging.LogLevel.Warn: return LogLevel.Warning; case Logging.LogLevel.Error: return LogLevel.Error; case Logging.LogLevel.Fatal: return LogLevel.Critical; } return LogLevel.None; } private static string MessageFormatter(object state, Exception exception) { return state.ToString(); } } } ================================================ FILE: src/Hangfire.NetCore/AspNetCore/AspNetCoreLogProvider.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Logging; using Microsoft.Extensions.Logging; namespace Hangfire.AspNetCore { public class AspNetCoreLogProvider : ILogProvider { private readonly ILoggerFactory _loggerFactory; public AspNetCoreLogProvider([NotNull] ILoggerFactory loggerFactory) { if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory)); _loggerFactory = loggerFactory; } public ILog GetLogger(string name) { return new AspNetCoreLog(_loggerFactory.CreateLogger(name)); } } } ================================================ FILE: src/Hangfire.NetCore/BackgroundJobServerHostedService.cs ================================================ // This file is part of Hangfire. Copyright © 2019 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . #if !NET451 && !NETSTANDARD1_3 using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Server; using Hangfire.States; using Microsoft.Extensions.Hosting; namespace Hangfire { public class BackgroundJobServerHostedService : IHostedService, IDisposable { private readonly BackgroundJobServerOptions _options; private readonly JobStorage _storage; private readonly IEnumerable _additionalProcesses; #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER private readonly IHostApplicationLifetime _hostApplicationLifetime; #endif private readonly IBackgroundJobFactory _factory; private readonly IBackgroundJobPerformer _performer; private readonly IBackgroundJobStateChanger _stateChanger; private IBackgroundProcessingServer _processingServer; public BackgroundJobServerHostedService( [NotNull] JobStorage storage, [NotNull] BackgroundJobServerOptions options, [NotNull] IEnumerable additionalProcesses) #pragma warning disable 618 : this(storage, options, additionalProcesses, null, null, null) #pragma warning restore 618 { } #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER public BackgroundJobServerHostedService( [NotNull] JobStorage storage, [NotNull] BackgroundJobServerOptions options, [NotNull] IEnumerable additionalProcesses, [CanBeNull] IHostApplicationLifetime hostApplicationLifetime) #pragma warning disable 618 : this(storage, options, additionalProcesses, null, null, null, hostApplicationLifetime) #pragma warning restore 618 { } #endif #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER [Obsolete("This constructor uses an obsolete constructor overload of the BackgroundJobServer type that will be removed in 2.0.0.")] public BackgroundJobServerHostedService( [NotNull] JobStorage storage, [NotNull] BackgroundJobServerOptions options, [NotNull] IEnumerable additionalProcesses, [CanBeNull] IBackgroundJobFactory factory, [CanBeNull] IBackgroundJobPerformer performer, [CanBeNull] IBackgroundJobStateChanger stateChanger) : this(storage, options, additionalProcesses, factory, performer, stateChanger, null) { } #endif [Obsolete("This constructor uses an obsolete constructor overload of the BackgroundJobServer type that will be removed in 2.0.0.")] public BackgroundJobServerHostedService( [NotNull] JobStorage storage, [NotNull] BackgroundJobServerOptions options, [NotNull] IEnumerable additionalProcesses, [CanBeNull] IBackgroundJobFactory factory, [CanBeNull] IBackgroundJobPerformer performer, [CanBeNull] IBackgroundJobStateChanger stateChanger #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER , [CanBeNull] IHostApplicationLifetime hostApplicationLifetime #endif ) { _options = options ?? throw new ArgumentNullException(nameof(options)); _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _additionalProcesses = additionalProcesses; _factory = factory; _performer = performer; _stateChanger = stateChanger; #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER _hostApplicationLifetime = hostApplicationLifetime; _hostApplicationLifetime?.ApplicationStopping.Register(SendStopSignal); #endif } public Task StartAsync(CancellationToken cancellationToken) { #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER if (_hostApplicationLifetime != null) { // https://github.com/HangfireIO/Hangfire/issues/2117 _hostApplicationLifetime.ApplicationStarted.Register(InitializeProcessingServer); } else #endif { InitializeProcessingServer(); } return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { var server = _processingServer; if (server == null) return; try { server.SendStop(); await server.WaitForShutdownAsync(cancellationToken); } catch (ObjectDisposedException) { // Due to a bug in ASP.NET Core's Testing package, the StopAsync method // can be called twice and from different threads, please see // https://github.com/dotnet/aspnetcore/issues/40271 for details. // This is not a case for regular applications but can fail integration tests // when the second call to the StopAsync method attempts to be performed on // an already disposed object. // This is not a big deal, however, it's still a workaround that should be // removed one day. // TODO: Remove this workaround and don't rely on this behavior for simplicity. } } public void Dispose() { _processingServer?.Dispose(); _processingServer = null; GC.SuppressFinalize(this); } private void InitializeProcessingServer() { _processingServer = _factory != null && _performer != null && _stateChanger != null #pragma warning disable 618 ? new BackgroundJobServer(_options, _storage, _additionalProcesses, null, null, _factory, _performer, _stateChanger) #pragma warning restore 618 : new BackgroundJobServer(_options, _storage, _additionalProcesses); } #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER private void SendStopSignal() { try { _processingServer?.SendStop(); } catch (ObjectDisposedException) { // Please see the comment regarding this exception above. } } #endif } } #endif ================================================ FILE: src/Hangfire.NetCore/BackgroundProcessingServerHostedService.cs ================================================ // This file is part of Hangfire. // Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . #if !NET451 && !NETSTANDARD1_3 using System; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Server; using Microsoft.Extensions.Hosting; namespace Hangfire { public sealed class BackgroundProcessingServerHostedService : IHostedService, IDisposable { private IBackgroundProcessingServer _server; #if NETSTANDARD2_1 public BackgroundProcessingServerHostedService([NotNull] IBackgroundProcessingServer server) : this(server, null) { } public BackgroundProcessingServerHostedService( [NotNull] IBackgroundProcessingServer server, [CanBeNull] IHostApplicationLifetime lifetime) { _server = server ?? throw new ArgumentNullException(nameof(server)); lifetime?.ApplicationStopping.Register(server.SendStop); } #else public BackgroundProcessingServerHostedService([NotNull] IBackgroundProcessingServer server) { _server = server ?? throw new ArgumentNullException(nameof(server)); } #endif public Task StartAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { _server?.SendStop(); return _server?.WaitForShutdownAsync(cancellationToken) ?? Task.CompletedTask; } public void Dispose() { _server?.Dispose(); _server = null; } } } #endif ================================================ FILE: src/Hangfire.NetCore/DefaultClientManagerFactory.cs ================================================ // This file is part of Hangfire. Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Common; using Microsoft.Extensions.DependencyInjection; namespace Hangfire { internal sealed class DefaultClientManagerFactory : IBackgroundJobClientFactoryV2, IRecurringJobManagerFactoryV2 { private readonly IServiceProvider _serviceProvider; public DefaultClientManagerFactory([NotNull] IServiceProvider serviceProvider) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } public IBackgroundJobClientV2 GetClientV2(JobStorage storage) { if (HangfireServiceCollectionExtensions.GetInternalServices(_serviceProvider, out var factory, out var stateChanger, out _)) { return new BackgroundJobClient(storage, factory, stateChanger); } return new BackgroundJobClient( storage, _serviceProvider.GetRequiredService()); } public IBackgroundJobClient GetClient(JobStorage storage) { return GetClientV2(storage); } public IRecurringJobManagerV2 GetManagerV2(JobStorage storage) { if (HangfireServiceCollectionExtensions.GetInternalServices(_serviceProvider, out var factory, out _, out _)) { return new RecurringJobManager( storage, factory, _serviceProvider.GetRequiredService()); } return new RecurringJobManager( storage, _serviceProvider.GetRequiredService(), _serviceProvider.GetRequiredService()); } public IRecurringJobManager GetManager(JobStorage storage) { return GetManagerV2(storage); } } } ================================================ FILE: src/Hangfire.NetCore/Hangfire.NetCore.csproj ================================================  net451;net461;netstandard1.3;netstandard2.0;netstandard2.1 true Hangfire ================================================ FILE: src/Hangfire.NetCore/HangfireServiceCollectionExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2016 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using Hangfire.Annotations; using Hangfire.AspNetCore; using Hangfire.Client; using Hangfire.Common; using Hangfire.Dashboard; using Hangfire.Server; using Hangfire.States; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; #if !NET451 && !NETSTANDARD1_3 using Microsoft.Extensions.Hosting; #endif namespace Hangfire { public static class HangfireServiceCollectionExtensions { public static IServiceCollection AddHangfire( [NotNull] this IServiceCollection services, [NotNull] Action configuration) { return AddHangfire(services, (provider, config) => configuration(config)); } public static IServiceCollection AddHangfire( [NotNull] this IServiceCollection services, [NotNull] Action configuration) { if (services == null) throw new ArgumentNullException(nameof(services)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); services.TryAddSingletonChecked(static _ => JobStorage.Current); services.TryAddSingletonChecked(static _ => JobActivator.Current); services.TryAddSingleton(static _ => DashboardRoutes.Routes); services.TryAddSingleton(static _ => JobFilterProviders.Providers); services.TryAddSingleton(static _ => new DefaultTimeZoneResolver()); services.TryAddSingleton(static x => new DefaultClientManagerFactory(x)); services.TryAddSingletonChecked(static x => x.GetService()); services.TryAddSingletonChecked(static x => x.GetService()); services.TryAddSingletonChecked(static x => x.GetService()); services.TryAddSingletonChecked(static x => x.GetService()); services.TryAddSingletonChecked(static x => x .GetService().GetClient(x.GetService())); services.TryAddSingletonChecked(static x => x .GetService().GetClientV2(x.GetService())); services.TryAddSingletonChecked(static x => x .GetService().GetManager(x.GetService())); services.TryAddSingletonChecked(static x => x .GetService().GetManagerV2(x.GetService())); // IGlobalConfiguration serves as a marker indicating that Hangfire's services // were added to the service container (checked by IApplicationBuilder extensions). // // Being a singleton, it also guarantees that the configuration callback will be // executed just once upon initialization, so there's no need to double-check that. // // It should never be replaced by another implementation !!! // AddSingleton() will throw an exception if it was already registered services.AddSingleton(serviceProvider => { var configurationInstance = GlobalConfiguration.Configuration; // init defaults for log provider and job activator // they may be overwritten by the configuration callback later var loggerFactory = serviceProvider.GetService(); if (loggerFactory != null) { configurationInstance.UseLogProvider(new AspNetCoreLogProvider(loggerFactory)); } var scopeFactory = serviceProvider.GetService(); if (scopeFactory != null) { configurationInstance.UseActivator(new AspNetCoreJobActivator(scopeFactory)); } // do configuration inside callback configuration(serviceProvider, configurationInstance); return configurationInstance; }); return services; } #if !NET451 && !NETSTANDARD1_3 public static IServiceCollection AddHangfireServer( [NotNull] this IServiceCollection services, [NotNull] Action optionsAction) { if (services == null) throw new ArgumentNullException(nameof(services)); if (optionsAction == null) throw new ArgumentNullException(nameof(optionsAction)); return AddHangfireServerInner(services, null, null, (provider, options) => optionsAction(options)); } public static IServiceCollection AddHangfireServer( [NotNull] this IServiceCollection services, [NotNull] Action optionsAction) { if (services == null) throw new ArgumentNullException(nameof(services)); if (optionsAction == null) throw new ArgumentNullException(nameof(optionsAction)); return AddHangfireServerInner(services, null, null, optionsAction); } public static IServiceCollection AddHangfireServer( [NotNull] this IServiceCollection services, [NotNull] Action optionsAction, [NotNull] JobStorage storage) { if (services == null) throw new ArgumentNullException(nameof(services)); if (optionsAction == null) throw new ArgumentNullException(nameof(optionsAction)); if (storage == null) throw new ArgumentNullException(nameof(storage)); return AddHangfireServerInner(services, storage, null, optionsAction); } public static IServiceCollection AddHangfireServer( [NotNull] this IServiceCollection services, [NotNull] Action optionsAction, [NotNull] JobStorage storage, [NotNull] IEnumerable additionalProcesses) { if (services == null) throw new ArgumentNullException(nameof(services)); if (optionsAction == null) throw new ArgumentNullException(nameof(optionsAction)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (additionalProcesses == null) throw new ArgumentNullException(nameof(additionalProcesses)); return AddHangfireServerInner(services, storage, additionalProcesses, optionsAction); } public static IServiceCollection AddHangfireServer([NotNull] this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); return AddHangfireServerInner(services, null, null); } public static IServiceCollection AddHangfireServer( [NotNull] this IServiceCollection services, [NotNull] JobStorage storage) { if (services == null) throw new ArgumentNullException(nameof(services)); if (storage == null) throw new ArgumentNullException(nameof(storage)); return AddHangfireServerInner(services, storage, null); } public static IServiceCollection AddHangfireServer( [NotNull] this IServiceCollection services, [NotNull] JobStorage storage, [NotNull] IEnumerable additionalProcesses) { if (services == null) throw new ArgumentNullException(nameof(services)); if (storage == null) throw new ArgumentNullException(nameof(storage)); if (additionalProcesses == null) throw new ArgumentNullException(nameof(additionalProcesses)); return AddHangfireServerInner(services, storage, additionalProcesses); } private static IServiceCollection AddHangfireServerInner( [NotNull] IServiceCollection services, [CanBeNull] JobStorage storage, [CanBeNull] IEnumerable additionalProcesses) { services.AddTransient(provider => { var options = provider.GetService() ?? new BackgroundJobServerOptions(); return CreateBackgroundJobServerHostedService(provider, storage, additionalProcesses, options); }); return services; } private static IServiceCollection AddHangfireServerInner( [NotNull] IServiceCollection services, [CanBeNull] JobStorage storage, [CanBeNull] IEnumerable additionalProcesses, [NotNull] Action optionsAction) { services.AddTransient(provider => { var options = new BackgroundJobServerOptions(); optionsAction(provider, options); return CreateBackgroundJobServerHostedService(provider, storage, additionalProcesses, options); }); return services; } public static IServiceCollection AddHangfireServer( [NotNull] this IServiceCollection services, [NotNull] Func implementationFactory) { if (services == null) throw new ArgumentNullException(nameof(services)); if (implementationFactory == null) throw new ArgumentNullException(nameof(implementationFactory)); services.AddTransient( provider => new BackgroundProcessingServerHostedService( implementationFactory(provider) #if NETSTANDARD2_1 , provider.GetService() #endif )); return services; } private static BackgroundJobServerHostedService CreateBackgroundJobServerHostedService( IServiceProvider provider, JobStorage storage, IEnumerable additionalProcesses, BackgroundJobServerOptions options) { ThrowIfNotConfigured(provider); storage = storage ?? provider.GetService() ?? JobStorage.Current; additionalProcesses = additionalProcesses ?? provider.GetServices(); options.Activator = options.Activator ?? provider.GetService(); options.FilterProvider = options.FilterProvider ?? provider.GetService(); options.TimeZoneResolver = options.TimeZoneResolver ?? provider.GetService(); GetInternalServices(provider, out var factory, out var stateChanger, out var performer); #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER var lifetime = provider.GetService(); #endif #pragma warning disable 618 return new BackgroundJobServerHostedService( #pragma warning restore 618 storage, options, additionalProcesses, factory, performer, stateChanger #if NETSTANDARD2_1 || NETCOREAPP3_0_OR_GREATER , lifetime #endif ); } #endif public static bool GetInternalServices( IServiceProvider provider, out IBackgroundJobFactory factory, out IBackgroundJobStateChanger stateChanger, out IBackgroundJobPerformer performer) { factory = provider.GetService(); performer = provider.GetService(); stateChanger = provider.GetService(); if (factory != null && performer != null && stateChanger != null) { return true; } factory = null; performer = null; stateChanger = null; return false; } private static void TryAddSingletonChecked( [NotNull] this IServiceCollection serviceCollection, [NotNull] Func implementationFactory) where T : class { serviceCollection.TryAddSingleton(serviceProvider => { if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider)); // ensure the configuration was performed serviceProvider.GetRequiredService(); return implementationFactory(serviceProvider); }); } public static void ThrowIfNotConfigured(IServiceProvider serviceProvider) { var configuration = serviceProvider.GetService(); if (configuration == null) { throw new InvalidOperationException( "Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddHangfire' inside the call to 'ConfigureServices(...)' in the application startup code."); } } } } ================================================ FILE: src/Hangfire.NetCore/IBackgroundJobClientFactory.cs ================================================ // This file is part of Hangfire. Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire { public interface IBackgroundJobClientFactoryV2 : IBackgroundJobClientFactory { IBackgroundJobClientV2 GetClientV2(JobStorage storage); } public interface IBackgroundJobClientFactory { IBackgroundJobClient GetClient(JobStorage storage); } } ================================================ FILE: src/Hangfire.NetCore/IRecurringJobManagerFactory.cs ================================================ // This file is part of Hangfire. Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire { public interface IRecurringJobManagerFactoryV2 : IRecurringJobManagerFactory { IRecurringJobManagerV2 GetManagerV2(JobStorage storage); } public interface IRecurringJobManagerFactory { IRecurringJobManager GetManager(JobStorage storage); } } ================================================ FILE: src/Hangfire.NetCore/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.1": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "+XwaNo3o9RhLQhUnnOBCaukeRi1X9yYc0Fzye9RlErSflKZdw0VgHtn6rvKo0FTionsW0x8QVULhKH+nkqVjQA==", "dependencies": { "System.ComponentModel": "4.0.1", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "wHT6oY50q36mAXBRKtFaB7u07WxKC5u2M8fi3PqHOOnHyUo9gD0u1TlCNR8UObHQxKMYwqlgI8TLcErpt29n8A==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net451": "1.0.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.NETFramework.ReferenceAssemblies.net451": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "vVPinxdLrwoX81ApbNIHDBI6qymQEy8eSOxDNBgKJtc2+cifnF0oT1U2d3EFx+V5O68yaqna2myZJNsgKCpVkA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==" }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==" }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "oBZFnm7seFiVfugsIyOvQCWobNZs7FzqDV/B7tx20Ep/l3UUFCPDkdTnCNaJZTU27zjeODmy2C/cP60u3D4c9w==" }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==" }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==" }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==" }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==" }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==" }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==" }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==" }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==" }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } } }, ".NETFramework,Version=v4.6.1": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "eUdJ0Q/GfVyUJc0Jal5L1QZLceL78pvEM9wEKcHeI24KorqMDoVX+gWsMGLulQMfOwsUaPtkpQM2pFERTzSfSg==" }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "qPG6Ip/AdHxMJ7j3z8FkkpCbV8yjtiFpf/aOpN3TwfJWbtYpN+BKV8Q+pqPMgk7XZivcju9yARaEVCS++hWopA==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.NETFramework.ReferenceAssemblies.net461": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } } }, ".NETStandard,Version=v1.3": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "+XwaNo3o9RhLQhUnnOBCaukeRi1X9yYc0Fzye9RlErSflKZdw0VgHtn6rvKo0FTionsW0x8QVULhKH+nkqVjQA==", "dependencies": { "System.ComponentModel": "4.0.1", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", "contentHash": "wHT6oY50q36mAXBRKtFaB7u07WxKC5u2M8fi3PqHOOnHyUo9gD0u1TlCNR8UObHQxKMYwqlgI8TLcErpt29n8A==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "NETStandard.Library": { "type": "Direct", "requested": "[1.6.1, )", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "17h8b5mXa87XYKrrVqdgZ38JefSUqLChUQpXgSnpzsM0nDOhE40FTeNWOJ/YmySGV6tG6T8+hjz6vxbknHJr6A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Threading": "4.0.11" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "9.0.1", "contentHash": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", "dependencies": { "Microsoft.CSharp": "4.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Serialization.Primitives": "4.1.1", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11" } }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "oBZFnm7seFiVfugsIyOvQCWobNZs7FzqDV/B7tx20Ep/l3UUFCPDkdTnCNaJZTU27zjeODmy2C/cP60u3D4c9w==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Runtime.Serialization.Primitives": { "type": "Transitive", "resolved": "4.1.1", "contentHash": "HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==", "dependencies": { "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Threading.ThreadPool": { "type": "Transitive", "resolved": "4.0.10", "contentHash": "IMXgB5Vf/5Qw1kpoVgJMOvUO1l32aC+qC3OaIZjWJOjvcxuxNWOK2ZTWWYXfij22NHxT2j1yWX5vlAeQWld9vA==", "dependencies": { "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "NETStandard.Library": "[1.6.1, )", "Newtonsoft.Json": "[9.0.1, )", "System.Threading.Thread": "[4.0.0, )", "System.Threading.ThreadPool": "[4.0.10, )" } } }, ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "eUdJ0Q/GfVyUJc0Jal5L1QZLceL78pvEM9wEKcHeI24KorqMDoVX+gWsMGLulQMfOwsUaPtkpQM2pFERTzSfSg==" }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "qPG6Ip/AdHxMJ7j3z8FkkpCbV8yjtiFpf/aOpN3TwfJWbtYpN+BKV8Q+pqPMgk7XZivcju9yARaEVCS++hWopA==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } } }, ".NETStandard,Version=v2.1": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", "requested": "[3.0.0, )", "resolved": "3.0.0", "contentHash": "ofQRroDlzJ0xKOtzNuaVt6QKNImFkhkG0lIMpGl7PtXnIf5SuLWBeiQZAP8DNSxDBJJdcsPkiJiMYK2WA5H8dQ==" }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Direct", "requested": "[3.0.0, )", "resolved": "3.0.0", "contentHash": "qeDWS5ErmkUN96BdQqpmeCmLk5HJWQ/SPw3ux5v5/Qb0hKZS5wojBMulnBC7JUEiBwg7Ir71Yjf1lFiRT5MdtQ==", "dependencies": { "Microsoft.Extensions.Configuration.Abstractions": "3.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "3.0.0", "Microsoft.Extensions.FileProviders.Abstractions": "3.0.0", "Microsoft.Extensions.Logging.Abstractions": "3.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", "requested": "[3.0.0, )", "resolved": "3.0.0", "contentHash": "+PsosTYZn+omucI0ff9eywo9QcPLwcbIWf7dz7ZLM1zGR8gVZXJ3wo6+tkuIedUNW5iWENlVJPEvrGjiVeoNNQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "Lge/PbXC53jI1MF2J92X5EZOeKV8Q/rlB1aV3H9I/ZTDyQGOyBcL03IAvnviWpHKj43BDkNy6kU2KKoh8kAS0g==", "dependencies": { "Microsoft.Extensions.Primitives": "3.0.0" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "kahEeykb6FyQytoZNNXuz74X85B4weIEt8Kd+0klK48bkXDWOIHAOvNjlGsPMcS9CL935Te8QGQS83JqCbpdHA==", "dependencies": { "Microsoft.Extensions.Primitives": "3.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "3.0.0", "contentHash": "6gwewTbmOh+ZVBicVkL1XRp79sx4O7BVY6Yy+7OYZdwn3pyOKe9lOam+3gXJ3TZMjhJZdV0Ub8hxHt2vkrmN5Q==", "dependencies": { "System.Memory": "4.5.2", "System.Runtime.CompilerServices.Unsafe": "4.6.0" } }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==" }, "System.Memory": { "type": "Transitive", "resolved": "4.5.2", "contentHash": "fvq1GNmUFwbKv+aLVYYdgu/+gc8Nu9oFujOxIjPrsf+meis9JBzTPDL6aP/eeGOz9yPj6rRLUbOjKMpsMEWpNg==", "dependencies": { "System.Buffers": "4.4.0", "System.Numerics.Vectors": "4.4.0", "System.Runtime.CompilerServices.Unsafe": "4.5.2" } }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "HxozeSlipUK7dAroTYwIcGwKDeOVpQnJlpVaOkBz7CM4TsE5b/tKlQBZecTjh6FzcSbxndYaxxpsBMz+wMJeyw==" }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } } } } } ================================================ FILE: src/Hangfire.SqlServer/Constants.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.SqlServer { internal sealed class Constants { public static readonly string DefaultSchema = "HangFire"; } } ================================================ FILE: src/Hangfire.SqlServer/CountersAggregator.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; using Dapper; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Server; namespace Hangfire.SqlServer { #pragma warning disable 618 internal sealed class CountersAggregator : IServerComponent, IBackgroundProcess #pragma warning restore 618 { // This number should be high enough to aggregate counters efficiently, // but low enough to not to cause large amount of row locks to be taken. // Lock escalation to page locks may pause the background processing. private const int NumberOfRecordsInSinglePass = 1000; private static readonly TimeSpan DelayBetweenPasses = TimeSpan.FromMilliseconds(500); private readonly ILog _logger = LogProvider.For(); private readonly SqlServerStorage _storage; private readonly TimeSpan _interval; public CountersAggregator(SqlServerStorage storage, TimeSpan interval) { if (storage == null) throw new ArgumentNullException(nameof(storage)); _storage = storage; _interval = interval; } public void Execute(BackgroundProcessContext context) { if (context.Storage is not SqlServerStorage storage) { return; } ExecuteCore(storage, context.StoppingToken); } public void Execute(CancellationToken cancellationToken) { ExecuteCore(_storage, cancellationToken); } private void ExecuteCore(SqlServerStorage storage, CancellationToken cancellationToken) { _logger.Debug("Aggregating records in 'Counter' table..."); int removedCount; do { removedCount = storage.UseConnection(null, static (storage, connection) => connection.Execute( GetAggregationQuery(storage), new { now = DateTime.UtcNow, count = NumberOfRecordsInSinglePass }, commandTimeout: 0)); if (removedCount >= NumberOfRecordsInSinglePass) { cancellationToken.Wait(DelayBetweenPasses); cancellationToken.ThrowIfCancellationRequested(); } // ReSharper disable once LoopVariableIsNeverChangedInsideLoop } while (removedCount >= NumberOfRecordsInSinglePass); _logger.Trace("Records from the 'Counter' table aggregated."); cancellationToken.Wait(_interval); } public override string ToString() { return GetType().ToString(); } private static string GetAggregationQuery(SqlServerStorage storage) { // Starting from SQL Server 2014 it's possible to get a query with // much lower cost by adding a clustered index on [Key] column. // However extended support for SQL Server 2012 SP4 ends only on // July 12, 2022. return storage.GetQueryFromTemplate(static schemaName => $@"DECLARE @RecordsToAggregate TABLE ( [Key] NVARCHAR(100) COLLATE DATABASE_DEFAULT NOT NULL, [Value] INT NOT NULL, [ExpireAt] DATETIME NULL ) SET XACT_ABORT ON SET TRANSACTION ISOLATION LEVEL READ COMMITTED SET DEADLOCK_PRIORITY LOW BEGIN TRAN DELETE TOP (@count) C OUTPUT DELETED.[Key], DELETED.[Value], DELETED.[ExpireAt] INTO @RecordsToAggregate FROM [{schemaName}].[Counter] C WITH (READPAST, XLOCK, INDEX(0)) SET NOCOUNT ON ;MERGE [{schemaName}].[AggregatedCounter] WITH (FORCESEEK, HOLDLOCK) AS [Target] USING ( SELECT [Key], SUM([Value]) as [Value], MAX([ExpireAt]) AS [ExpireAt] FROM @RecordsToAggregate GROUP BY [Key]) AS [Source] ([Key], [Value], [ExpireAt]) ON [Target].[Key] COLLATE DATABASE_DEFAULT = [Source].[Key] COLLATE DATABASE_DEFAULT WHEN MATCHED THEN UPDATE SET [Target].[Value] = [Target].[Value] + [Source].[Value], [Target].[ExpireAt] = (SELECT MAX([ExpireAt]) FROM (VALUES ([Source].ExpireAt), ([Target].[ExpireAt])) AS MaxExpireAt([ExpireAt])) WHEN NOT MATCHED THEN INSERT ([Key], [Value], [ExpireAt]) VALUES ([Source].[Key], [Source].[Value], [Source].[ExpireAt]); COMMIT TRAN"); } } } ================================================ FILE: src/Hangfire.SqlServer/DbCommandExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2024 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using Hangfire.Annotations; namespace Hangfire.SqlServer { internal static class DbCommandExtensions { private static readonly ConcurrentDictionary>, string> ExpandedQueries = new ConcurrentDictionary>, string>(); public static DbCommand Create( [NotNull] this DbConnection connection, [NotNull] string text, CommandType type = CommandType.Text, int? timeout = null) { if (connection == null) throw new ArgumentNullException(nameof(connection)); if (text == null) throw new ArgumentNullException(nameof(text)); var command = connection.CreateCommand(); command.CommandType = type; command.CommandText = text; if (timeout.HasValue) { command.CommandTimeout = timeout.Value; } return command; } public static DbCommand AddParameter( [NotNull] this DbCommand command, [NotNull] string parameterName, [CanBeNull] object value, DbType dbType, [CanBeNull] int? size = null) { if (command == null) throw new ArgumentNullException(nameof(command)); if (parameterName == null) throw new ArgumentNullException(nameof(parameterName)); var parameter = AddParameterInternal(command, parameterName, dbType, size); parameter.Value = value ?? DBNull.Value; return command; } public static DbCommand AddReturnParameter( [NotNull] this DbCommand command, string parameterName, out DbParameter parameter, DbType dbType, int? size = null) { if (command == null) throw new ArgumentNullException(nameof(command)); if (parameterName == null) throw new ArgumentNullException(nameof(parameterName)); parameter = AddParameterInternal(command, parameterName, dbType, size); parameter.Direction = ParameterDirection.ReturnValue; parameter.Value = DBNull.Value; return command; } public static DbCommand AddExpandedParameter( [NotNull] this DbCommand command, [NotNull] string parameterName, [NotNull] T[] parameterValues, DbType parameterType, int? parameterSize = null) { if (command == null) throw new ArgumentNullException(nameof(command)); if (parameterName == null) throw new ArgumentNullException(nameof(parameterName)); if (parameterValues == null) throw new ArgumentNullException(nameof(parameterValues)); command.CommandText = ExpandedQueries.GetOrAdd( new KeyValuePair>(command.CommandText, new KeyValuePair(parameterName, parameterValues.Length)), static pair => { return pair.Key.Replace( pair.Value.Key, "(" + String.Join(",", Enumerable.Range(0, pair.Value.Value).Select(i => pair.Value.Key + i.ToString(CultureInfo.InvariantCulture))) + ")"); }); for (var i = 0; i < parameterValues.Length; i++) { var parameter = AddParameterInternal(command, parameterName + i.ToString(CultureInfo.InvariantCulture), parameterType, parameterSize); parameter.Value = (object)parameterValues[i] ?? DBNull.Value; } return command; } public static T GetParameterValue([NotNull] this DbParameter parameter) { if (parameter == null) throw new ArgumentNullException(nameof(parameter)); switch (parameter.Value) { case null or DBNull: return default; case T typed: return typed; default: var type = typeof(T); type = Nullable.GetUnderlyingType(type) ?? type; return (T)Convert.ChangeType(parameter.Value, type, CultureInfo.InvariantCulture); } } private static DbParameter AddParameterInternal( DbCommand command, string parameterName, DbType dbType, int? size) { var parameter = command.CreateParameter(); parameter.ParameterName = parameterName; parameter.DbType = dbType; if (size.HasValue) parameter.Size = size.Value; command.Parameters.Add(parameter); return parameter; } } } ================================================ FILE: src/Hangfire.SqlServer/DefaultInstall.sql ================================================  -- This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. -- -- Hangfire is free software: you can redistribute it and/or modify -- it under the terms of the GNU Lesser General Public License as -- published by the Free Software Foundation, either version 3 -- of the License, or any later version. -- -- Hangfire is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU Lesser General Public License for more details. -- -- You should have received a copy of the GNU Lesser General Public -- License along with Hangfire. If not, see . SET NOCOUNT ON SET XACT_ABORT ON DECLARE @TARGET_SCHEMA_VERSION INT; DECLARE @DISABLE_HEAVY_MIGRATIONS BIT; SET @TARGET_SCHEMA_VERSION = 9; --SET @DISABLE_HEAVY_MIGRATIONS = 1; PRINT 'Installing Hangfire SQL objects...'; BEGIN TRANSACTION; -- Acquire exclusive lock to prevent deadlocks caused by schema creation / version update DECLARE @SchemaLockResult INT; EXEC @SchemaLockResult = sp_getapplock @Resource = 'HangFire:SchemaLock', @LockMode = 'Exclusive' -- Create the database schema if it doesn't exists IF NOT EXISTS (SELECT [schema_id] FROM [sys].[schemas] WHERE [name] = 'HangFire') BEGIN EXEC (N'CREATE SCHEMA [HangFire]'); PRINT 'Created database schema [HangFire]'; END ELSE PRINT 'Database schema [HangFire] already exists'; DECLARE @SCHEMA_ID int; SELECT @SCHEMA_ID = [schema_id] FROM [sys].[schemas] WHERE [name] = 'HangFire'; -- Create the [HangFire].Schema table if not exists IF NOT EXISTS(SELECT [object_id] FROM [sys].[tables] WHERE [name] = 'Schema' AND [schema_id] = @SCHEMA_ID) BEGIN CREATE TABLE [HangFire].[Schema]( [Version] [int] NOT NULL, CONSTRAINT [PK_HangFire_Schema] PRIMARY KEY CLUSTERED ([Version] ASC) ); PRINT 'Created table [HangFire].[Schema]'; END ELSE PRINT 'Table [HangFire].[Schema] already exists'; DECLARE @CURRENT_SCHEMA_VERSION int; SELECT @CURRENT_SCHEMA_VERSION = [Version] FROM [HangFire].[Schema]; PRINT 'Current Hangfire schema version: ' + CASE WHEN @CURRENT_SCHEMA_VERSION IS NULL THEN 'none' ELSE CONVERT(nvarchar, @CURRENT_SCHEMA_VERSION) END; IF @CURRENT_SCHEMA_VERSION IS NOT NULL AND @CURRENT_SCHEMA_VERSION > @TARGET_SCHEMA_VERSION BEGIN ROLLBACK TRANSACTION; PRINT 'Hangfire current database schema version ' + CAST(@CURRENT_SCHEMA_VERSION AS NVARCHAR) + ' is newer than the configured SqlServerStorage schema version ' + CAST(@TARGET_SCHEMA_VERSION AS NVARCHAR) + '. Will not apply any migrations.'; RETURN; END -- Install [HangFire] schema objects IF @CURRENT_SCHEMA_VERSION IS NULL BEGIN IF @DISABLE_HEAVY_MIGRATIONS = 1 BEGIN SET @DISABLE_HEAVY_MIGRATIONS = 0; PRINT 'Enabling HEAVY_MIGRATIONS, because we are installing objects from scratch'; END PRINT 'Installing schema version 1'; -- Create job tables CREATE TABLE [HangFire].[Job] ( [Id] [int] IDENTITY(1,1) NOT NULL, [StateId] [int] NULL, [StateName] [nvarchar](20) NULL, -- To speed-up queries. [InvocationData] [nvarchar](max) NOT NULL, [Arguments] [nvarchar](max) NOT NULL, [CreatedAt] [datetime] NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Job] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[Job]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Job_StateName] ON [HangFire].[Job] ([StateName] ASC); PRINT 'Created index [IX_HangFire_Job_StateName]'; -- Job history table CREATE TABLE [HangFire].[State] ( [Id] [int] IDENTITY(1,1) NOT NULL, [JobId] [int] NOT NULL, [Name] [nvarchar](20) NOT NULL, [Reason] [nvarchar](100) NULL, [CreatedAt] [datetime] NOT NULL, [Data] [nvarchar](max) NULL, CONSTRAINT [PK_HangFire_State] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[State]'; ALTER TABLE [HangFire].[State] ADD CONSTRAINT [FK_HangFire_State_Job] FOREIGN KEY([JobId]) REFERENCES [HangFire].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Created constraint [FK_HangFire_State_Job]'; CREATE NONCLUSTERED INDEX [IX_HangFire_State_JobId] ON [HangFire].[State] ([JobId] ASC); PRINT 'Created index [IX_HangFire_State_JobId]'; -- Job parameters table CREATE TABLE [HangFire].[JobParameter]( [Id] [int] IDENTITY(1,1) NOT NULL, [JobId] [int] NOT NULL, [Name] [nvarchar](40) NOT NULL, [Value] [nvarchar](max) NULL, CONSTRAINT [PK_HangFire_JobParameter] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[JobParameter]'; ALTER TABLE [HangFire].[JobParameter] ADD CONSTRAINT [FK_HangFire_JobParameter_Job] FOREIGN KEY([JobId]) REFERENCES [HangFire].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Created constraint [FK_HangFire_JobParameter_Job]'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobParameter_JobIdAndName] ON [HangFire].[JobParameter] ( [JobId] ASC, [Name] ASC ); PRINT 'Created index [IX_HangFire_JobParameter_JobIdAndName]'; -- Job queue table CREATE TABLE [HangFire].[JobQueue]( [Id] [int] IDENTITY(1,1) NOT NULL, [JobId] [int] NOT NULL, [Queue] [nvarchar](20) NOT NULL, [FetchedAt] [datetime] NULL, CONSTRAINT [PK_HangFire_JobQueue] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[JobQueue]'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobQueue_JobIdAndQueue] ON [HangFire].[JobQueue] ( [JobId] ASC, [Queue] ASC ); PRINT 'Created index [IX_HangFire_JobQueue_JobIdAndQueue]'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [HangFire].[JobQueue] ( [Queue] ASC, [FetchedAt] ASC ); PRINT 'Created index [IX_HangFire_JobQueue_QueueAndFetchedAt]'; -- Servers table CREATE TABLE [HangFire].[Server]( [Id] [nvarchar](200) NOT NULL, [Data] [nvarchar](max) NULL, [LastHeartbeat] [datetime] NULL, CONSTRAINT [PK_HangFire_Server] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[Server]'; -- Extension tables CREATE TABLE [HangFire].[Hash]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Name] [nvarchar](40) NOT NULL, [StringValue] [nvarchar](max) NULL, [IntValue] [int] NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Hash] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[Hash]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Hash_KeyAndName] ON [HangFire].[Hash] ( [Key] ASC, [Name] ASC ); PRINT 'Created index [UX_HangFire_Hash_KeyAndName]'; CREATE TABLE [HangFire].[List]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Value] [nvarchar](max) NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_List] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[List]'; CREATE TABLE [HangFire].[Set]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Score] [float] NOT NULL, [Value] [nvarchar](256) NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Set] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[Set]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Set_KeyAndValue] ON [HangFire].[Set] ( [Key] ASC, [Value] ASC ); PRINT 'Created index [UX_HangFire_Set_KeyAndValue]'; CREATE TABLE [HangFire].[Value]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [StringValue] [nvarchar](max) NULL, [IntValue] [int] NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Value] PRIMARY KEY CLUSTERED ( [Id] ASC ) ); PRINT 'Created table [HangFire].[Value]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Value_Key] ON [HangFire].[Value] ( [Key] ASC ); PRINT 'Created index [UX_HangFire_Value_Key]'; CREATE TABLE [HangFire].[Counter]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Value] [tinyint] NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Counter] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[Counter]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Counter_Key] ON [HangFire].[Counter] ([Key] ASC) INCLUDE ([Value]); PRINT 'Created index [IX_HangFire_Counter_Key]'; SET @CURRENT_SCHEMA_VERSION = 1; END IF @CURRENT_SCHEMA_VERSION = 1 BEGIN PRINT 'Installing schema version 2'; -- https://github.com/odinserj/HangFire/issues/83 DROP INDEX [IX_HangFire_Counter_Key] ON [HangFire].[Counter]; ALTER TABLE [HangFire].[Counter] ALTER COLUMN [Value] SMALLINT NOT NULL; CREATE NONCLUSTERED INDEX [IX_HangFire_Counter_Key] ON [HangFire].[Counter] ([Key] ASC) INCLUDE ([Value]); PRINT 'Index [IX_HangFire_Counter_Key] re-created'; DROP TABLE [HangFire].[Value]; DROP TABLE [HangFire].[Hash]; PRINT 'Dropped tables [HangFire].[Value] and [HangFire].[Hash]' DELETE FROM [HangFire].[Server] WHERE [LastHeartbeat] IS NULL; ALTER TABLE [HangFire].[Server] ALTER COLUMN [LastHeartbeat] DATETIME NOT NULL; SET @CURRENT_SCHEMA_VERSION = 2; END IF @CURRENT_SCHEMA_VERSION = 2 BEGIN PRINT 'Installing schema version 3'; DROP INDEX [IX_HangFire_JobQueue_JobIdAndQueue] ON [HangFire].[JobQueue]; PRINT 'Dropped index [IX_HangFire_JobQueue_JobIdAndQueue]'; CREATE TABLE [HangFire].[Hash]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Field] [nvarchar](100) NOT NULL, [Value] [nvarchar](max) NULL, [ExpireAt] [datetime2](7) NULL, CONSTRAINT [PK_HangFire_Hash] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[Hash]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Hash_Key_Field] ON [HangFire].[Hash] ( [Key] ASC, [Field] ASC ); PRINT 'Created index [UX_HangFire_Hash_Key_Field]'; SET @CURRENT_SCHEMA_VERSION = 3; END IF @CURRENT_SCHEMA_VERSION = 3 BEGIN PRINT 'Installing schema version 4'; CREATE TABLE [HangFire].[AggregatedCounter] ( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Value] [bigint] NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_CounterAggregated] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [HangFire].[AggregatedCounter]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_CounterAggregated_Key] ON [HangFire].[AggregatedCounter] ( [Key] ASC ) INCLUDE ([Value]); PRINT 'Created index [UX_HangFire_CounterAggregated_Key]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Hash_ExpireAt] ON [HangFire].[Hash] ([ExpireAt]) INCLUDE ([Id]); CREATE NONCLUSTERED INDEX [IX_HangFire_Job_ExpireAt] ON [HangFire].[Job] ([ExpireAt]) INCLUDE ([Id]); CREATE NONCLUSTERED INDEX [IX_HangFire_List_ExpireAt] ON [HangFire].[List] ([ExpireAt]) INCLUDE ([Id]); CREATE NONCLUSTERED INDEX [IX_HangFire_Set_ExpireAt] ON [HangFire].[Set] ([ExpireAt]) INCLUDE ([Id]); PRINT 'Created indexes for [ExpireAt] columns'; CREATE NONCLUSTERED INDEX [IX_HangFire_Hash_Key] ON [HangFire].[Hash] ([Key] ASC) INCLUDE ([ExpireAt]); PRINT 'Created index [IX_HangFire_Hash_Key]'; CREATE NONCLUSTERED INDEX [IX_HangFire_List_Key] ON [HangFire].[List] ([Key] ASC) INCLUDE ([ExpireAt], [Value]); PRINT 'Created index [IX_HangFire_List_Key]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_Key] ON [HangFire].[Set] ([Key] ASC) INCLUDE ([ExpireAt], [Value]); PRINT 'Created index [IX_HangFire_Set_Key]'; SET @CURRENT_SCHEMA_VERSION = 4; END IF @CURRENT_SCHEMA_VERSION = 4 BEGIN PRINT 'Installing schema version 5'; DROP INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [HangFire].[JobQueue]; PRINT 'Dropped index [IX_HangFire_JobQueue_QueueAndFetchedAt] to modify the [HangFire].[JobQueue].[Queue] column'; ALTER TABLE [HangFire].[JobQueue] ALTER COLUMN [Queue] NVARCHAR (50) NOT NULL; PRINT 'Modified [HangFire].[JobQueue].[Queue] length to 50'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [HangFire].[JobQueue] ( [Queue] ASC, [FetchedAt] ASC ); PRINT 'Re-created index [IX_HangFire_JobQueue_QueueAndFetchedAt]'; ALTER TABLE [HangFire].[Server] DROP CONSTRAINT [PK_HangFire_Server] PRINT 'Dropped constraint [PK_HangFire_Server] to modify the [HangFire].[Server].[Id] column'; ALTER TABLE [HangFire].[Server] ALTER COLUMN [Id] NVARCHAR (200) NOT NULL; PRINT 'Modified [HangFire].[Server].[Id] length to 200'; ALTER TABLE [HangFire].[Server] ADD CONSTRAINT [PK_HangFire_Server] PRIMARY KEY CLUSTERED ( [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_Server]'; SET @CURRENT_SCHEMA_VERSION = 5; END IF @CURRENT_SCHEMA_VERSION = 5 AND @DISABLE_HEAVY_MIGRATIONS = 1 BEGIN PRINT 'Migration process STOPPED at schema version ' + CAST(@CURRENT_SCHEMA_VERSION AS NVARCHAR) + '. WILL NOT upgrade to schema version ' + CAST(@TARGET_SCHEMA_VERSION AS NVARCHAR) + ', because @DISABLE_HEAVY_MIGRATIONS option is set.'; END ELSE IF @CURRENT_SCHEMA_VERSION = 5 BEGIN PRINT 'Installing schema version 6'; -- First, we will drop all the secondary indexes on the HangFire.Set table, because we will -- modify that table, and unknown indexes may be added there (see https://github.com/HangfireIO/Hangfire/issues/844). -- So, we'll drop all of them, and then re-create the required index with a well-known name. DECLARE @dropIndexSql NVARCHAR(MAX) = N''; SELECT @dropIndexSql += N'DROP INDEX ' + QUOTENAME(SCHEMA_NAME(o.[schema_id])) + '.' + QUOTENAME(o.name) + '.' + QUOTENAME(i.name) + ';' FROM sys.indexes AS i INNER JOIN sys.tables AS o ON i.[object_id] = o.[object_id] WHERE i.is_primary_key = 0 AND i.index_id <> 0 AND o.is_ms_shipped = 0 AND SCHEMA_NAME(o.[schema_id]) = 'HangFire' AND o.name = 'Set'; EXEC sp_executesql @dropIndexSql; PRINT 'Dropped all secondary indexes on the [Set] table'; -- Next, we'll remove the unnecessary indexes. They were unnecessary in the previous schema, -- and are unnecessary in the new schema as well. We'll not re-create them. DROP INDEX [IX_HangFire_Hash_Key] ON [HangFire].[Hash]; PRINT 'Dropped unnecessary index [IX_HangFire_Hash_Key]'; -- Next, all the indexes that cover expiration will be filtered, to include only non-null values. This -- will prevent unnecessary index modifications – we are seeking these indexes only for non-null -- expiration time. Also, they include the Id column by a mistake. So we'll re-create them later in the -- migration. DROP INDEX [IX_HangFire_Hash_ExpireAt] ON [HangFire].[Hash]; PRINT 'Dropped index [IX_HangFire_Hash_ExpireAt]'; DROP INDEX [IX_HangFire_Job_ExpireAt] ON [HangFire].[Job]; PRINT 'Dropped index [IX_HangFire_Job_ExpireAt]'; DROP INDEX [IX_HangFire_List_ExpireAt] ON [HangFire].[List]; PRINT 'Dropped index [IX_HangFire_List_ExpireAt]'; -- IX_HangFire_Job_StateName index can also be optimized, since we are querying it only with a -- non-null state name. This will decrease the number of operations, when creating a background job. -- It will be recreated later in the migration. DROP INDEX [IX_HangFire_Job_StateName] ON [HangFire].Job; PRINT 'Dropped index [IX_HangFire_Job_StateName]'; -- Dropping foreign key constraints based on the JobId column, because we need to modify the underlying -- column type of the clustered index to BIGINT. We'll recreate them later in the migration. ALTER TABLE [HangFire].[JobParameter] DROP CONSTRAINT [FK_HangFire_JobParameter_Job]; PRINT 'Dropped constraint [FK_HangFire_JobParameter_Job]'; ALTER TABLE [HangFire].[State] DROP CONSTRAINT [FK_HangFire_State_Job]; PRINT 'Dropped constraint [FK_HangFire_State_Job]'; -- We are going to create composite clustered indexes that are more natural for the following tables, -- so the following indexes will be unnecessary. Natural sorting will keep related data close to each -- other, and simplify the index modifications by the cost of fragmentation and additional page splits. DROP INDEX [UX_HangFire_CounterAggregated_Key] ON [HangFire].[AggregatedCounter]; PRINT 'Dropped index [UX_HangFire_CounterAggregated_Key]'; DROP INDEX [IX_HangFire_Counter_Key] ON [HangFire].[Counter]; PRINT 'Dropped index [IX_HangFire_Counter_Key]'; DROP INDEX [IX_HangFire_JobParameter_JobIdAndName] ON [HangFire].[JobParameter]; PRINT 'Dropped index [IX_HangFire_JobParameter_JobIdAndName]'; DROP INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [HangFire].[JobQueue]; PRINT 'Dropped index [IX_HangFire_JobQueue_QueueAndFetchedAt]'; DROP INDEX [UX_HangFire_Hash_Key_Field] ON [HangFire].[Hash]; PRINT 'Dropped index [UX_HangFire_Hash_Key_Field]'; DROP INDEX [IX_HangFire_List_Key] ON [HangFire].[List]; PRINT 'Dropped index [IX_HangFire_List_Key]'; DROP INDEX [IX_HangFire_State_JobId] ON [HangFire].[State]; PRINT 'Dropped index [IX_HangFire_State_JobId]'; -- Then, we need to drop the primary key constraints, to modify id columns to the BIGINT type. Some of them -- will be re-created later in the migration. But some of them would be removed forever, because their -- uniqueness property sometimes unnecessary. ALTER TABLE [HangFire].[AggregatedCounter] DROP CONSTRAINT [PK_HangFire_CounterAggregated]; PRINT 'Dropped constraint [PK_HangFire_CounterAggregated]'; ALTER TABLE [HangFire].[Counter] DROP CONSTRAINT [PK_HangFire_Counter]; PRINT 'Dropped constraint [PK_HangFire_Counter]'; ALTER TABLE [HangFire].[Hash] DROP CONSTRAINT [PK_HangFire_Hash]; PRINT 'Dropped constraint [PK_HangFire_Hash]'; ALTER TABLE [HangFire].[Job] DROP CONSTRAINT [PK_HangFire_Job]; PRINT 'Dropped constraint [PK_HangFire_Job]'; ALTER TABLE [HangFire].[JobParameter] DROP CONSTRAINT [PK_HangFire_JobParameter]; PRINT 'Dropped constraint [PK_HangFire_JobParameter]'; ALTER TABLE [HangFire].[JobQueue] DROP CONSTRAINT [PK_HangFire_JobQueue]; PRINT 'Dropped constraint [PK_HangFire_JobQueue]'; ALTER TABLE [HangFire].[List] DROP CONSTRAINT [PK_HangFire_List]; PRINT 'Dropped constraint [PK_HangFire_List]'; ALTER TABLE [HangFire].[Set] DROP CONSTRAINT [PK_HangFire_Set]; PRINT 'Dropped constraint [PK_HangFire_Set]'; ALTER TABLE [HangFire].[State] DROP CONSTRAINT [PK_HangFire_State]; PRINT 'Dropped constraint [PK_HangFire_State]'; -- We are removing identity columns of the following tables completely, their clustered -- index will be based on natural values. So, instead of modifying them to BIGINT, we -- are dropping them. ALTER TABLE [HangFire].[AggregatedCounter] DROP COLUMN [Id]; PRINT 'Dropped [AggregatedCounter].[Id] column, we will cluster on [Key] column with uniqufier'; ALTER TABLE [HangFire].[Counter] DROP COLUMN [Id]; PRINT 'Dropped [Counter].[Id] column, we will cluster on [Key] column'; ALTER TABLE [HangFire].[Hash] DROP COLUMN [Id]; PRINT 'Dropped [Hash].[Id] column, we will cluster on [Key]/[Field] columns'; ALTER TABLE [HangFire].[Set] DROP COLUMN [Id]; PRINT 'Dropped [Set].[Id] column, we will cluster on [Key]/[Value] columns'; ALTER TABLE [HangFire].[JobParameter] DROP COLUMN [Id]; PRINT 'Dropped [JobParameter].[Id] column, we will cluster on [JobId]/[Name] columns'; -- Then we need to modify all the remaining Id columns to be of type BIGINT. ALTER TABLE [HangFire].[List] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [List].[Id] type to BIGINT'; ALTER TABLE [HangFire].[Job] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [Job].[Id] type to BIGINT'; ALTER TABLE [HangFire].[Job] ALTER COLUMN [StateId] BIGINT NULL; PRINT 'Modified [Job].[StateId] type to BIGINT'; ALTER TABLE [HangFire].[JobParameter] ALTER COLUMN [JobId] BIGINT NOT NULL; PRINT 'Modified [JobParameter].[JobId] type to BIGINT'; ALTER TABLE [HangFire].[JobQueue] ALTER COLUMN [JobId] BIGINT NOT NULL; PRINT 'Modified [JobQueue].[JobId] type to BIGINT'; ALTER TABLE [HangFire].[JobQueue] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [JobQueue].[Id] type to BIGINT'; ALTER TABLE [HangFire].[State] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [State].[Id] type to BIGINT'; ALTER TABLE [HangFire].[State] ALTER COLUMN [JobId] BIGINT NOT NULL; PRINT 'Modified [State].[JobId] type to BIGINT'; ALTER TABLE [HangFire].[Counter] ALTER COLUMN [Value] INT NOT NULL; PRINT 'Modified [Counter].[Value] type to INT'; -- Adding back all the Primary Key constraints or clustered indexes where PKs aren't appropriate. ALTER TABLE [HangFire].[AggregatedCounter] ADD CONSTRAINT [PK_HangFire_CounterAggregated] PRIMARY KEY CLUSTERED ( [Key] ASC ); PRINT 'Re-created constraint [PK_HangFire_CounterAggregated]'; CREATE CLUSTERED INDEX [CX_HangFire_Counter] ON [HangFire].[Counter] ([Key]); PRINT 'Created clustered index [CX_HangFire_Counter]'; ALTER TABLE [HangFire].[Hash] ADD CONSTRAINT [PK_HangFire_Hash] PRIMARY KEY CLUSTERED ( [Key] ASC, [Field] ASC ); PRINT 'Re-created constraint [PK_HangFire_Hash]'; ALTER TABLE [HangFire].[Job] ADD CONSTRAINT [PK_HangFire_Job] PRIMARY KEY CLUSTERED ([Id] ASC); PRINT 'Re-created constraint [PK_HangFire_Job]'; ALTER TABLE [HangFire].[JobParameter] ADD CONSTRAINT [PK_HangFire_JobParameter] PRIMARY KEY CLUSTERED ( [JobId] ASC, [Name] ASC ); PRINT 'Re-created constraint [PK_HangFire_JobParameter]'; ALTER TABLE [HangFire].[JobQueue] ADD CONSTRAINT [PK_HangFire_JobQueue] PRIMARY KEY CLUSTERED ( [Queue] ASC, [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_JobQueue]'; ALTER TABLE [HangFire].[List] ADD CONSTRAINT [PK_HangFire_List] PRIMARY KEY CLUSTERED ( [Key] ASC, [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_List]'; ALTER TABLE [HangFire].[Set] ADD CONSTRAINT [PK_HangFire_Set] PRIMARY KEY CLUSTERED ( [Key] ASC, [Value] ASC ); PRINT 'Re-created constraint [PK_HangFire_Set]'; ALTER TABLE [HangFire].[State] ADD CONSTRAINT [PK_HangFire_State] PRIMARY KEY CLUSTERED ( [JobId] ASC, [Id] ); PRINT 'Re-created constraint [PK_HangFire_State]'; -- Creating secondary, nonclustered indexes CREATE NONCLUSTERED INDEX [IX_HangFire_Job_StateName] ON [HangFire].[Job] ([StateName]) WHERE [StateName] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Job_StateName]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_Score] ON [HangFire].[Set] ([Score]) WHERE [Score] IS NOT NULL; PRINT 'Created index [IX_HangFire_Set_Score]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Server_LastHeartbeat] ON [HangFire].[Server] ([LastHeartbeat]); PRINT 'Created index [IX_HangFire_Server_LastHeartbeat]'; -- Creating filtered indexes for ExpireAt columns CREATE NONCLUSTERED INDEX [IX_HangFire_AggregatedCounter_ExpireAt] ON [HangFire].[AggregatedCounter] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Created index [IX_HangFire_AggregatedCounter_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Hash_ExpireAt] ON [HangFire].[Hash] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Hash_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Job_ExpireAt] ON [HangFire].[Job] ([ExpireAt]) INCLUDE ([StateName]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Job_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_List_ExpireAt] ON [HangFire].[List] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_List_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_ExpireAt] ON [HangFire].[Set] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Set_ExpireAt]'; -- Restoring foreign keys ALTER TABLE [HangFire].[State] ADD CONSTRAINT [FK_HangFire_State_Job] FOREIGN KEY([JobId]) REFERENCES [HangFire].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Re-created constraint [FK_HangFire_State_Job]'; ALTER TABLE [HangFire].[JobParameter] ADD CONSTRAINT [FK_HangFire_JobParameter_Job] FOREIGN KEY([JobId]) REFERENCES [HangFire].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Re-created constraint [FK_HangFire_JobParameter_Job]'; SET @CURRENT_SCHEMA_VERSION = 6; END IF @CURRENT_SCHEMA_VERSION = 6 BEGIN PRINT 'Installing schema version 7'; DROP INDEX [IX_HangFire_Set_Score] ON [HangFire].[Set]; PRINT 'Dropped index [IX_HangFire_Set_Score]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_Score] ON [HangFire].[Set] ([Key], [Score]); PRINT 'Created index [IX_HangFire_Set_Score] with the proper composite key'; SET @CURRENT_SCHEMA_VERSION = 7; END IF @CURRENT_SCHEMA_VERSION = 7 AND @DISABLE_HEAVY_MIGRATIONS = 1 BEGIN PRINT 'Migration process STOPPED at schema version ' + CAST(@CURRENT_SCHEMA_VERSION AS NVARCHAR) + '. WILL NOT upgrade to schema version ' + CAST(@TARGET_SCHEMA_VERSION AS NVARCHAR) + ', because @DISABLE_HEAVY_MIGRATIONS option is set.'; END ELSE IF @CURRENT_SCHEMA_VERSION = 7 BEGIN PRINT 'Installing schema version 8'; ALTER TABLE [HangFire].[Server] DROP CONSTRAINT [PK_HangFire_Server] PRINT 'Dropped constraint [PK_HangFire_Server] to modify the [HangFire].[Server].[Id] column'; ALTER TABLE [HangFire].[Server] ALTER COLUMN [Id] NVARCHAR (200) NOT NULL; PRINT 'Modified [HangFire].[Server].[Id] length to 200'; ALTER TABLE [HangFire].[Server] ADD CONSTRAINT [PK_HangFire_Server] PRIMARY KEY CLUSTERED ([Id] ASC); PRINT 'Re-created constraint [PK_HangFire_Server]'; -- Nothing complicated - we just collecting all the secondary indexes and primary key names to delete them. -- We should expect nothing here, because custom columns and indexes can be applied for the [Counter] table -- to make replication work on Microsoft Azure, like in the issue below. -- https://github.com/HangfireIO/Hangfire/issues/1500 DECLARE @dropIndexSql2 NVARCHAR(MAX) = N''; SELECT @dropIndexSql2 += N'DROP INDEX ' + QUOTENAME(SCHEMA_NAME(o.[schema_id])) + '.' + QUOTENAME(o.name) + '.' + QUOTENAME(i.name) + ';' FROM sys.indexes AS i INNER JOIN sys.tables AS o ON i.[object_id] = o.[object_id] WHERE i.is_primary_key = 0 AND i.index_id <> 0 AND o.is_ms_shipped = 0 AND SCHEMA_NAME(o.[schema_id]) = 'HangFire' AND o.name = 'Counter'; SELECT @dropIndexSql2 += N'ALTER TABLE' + QUOTENAME(SCHEMA_NAME(o.[schema_id])) + '.' + QUOTENAME(o.name) + ' DROP CONSTRAINT ' + QUOTENAME(c.name) + ';' FROM sys.key_constraints c INNER JOIN sys.tables AS o ON c.[parent_object_id] = o.[object_id] WHERE o.is_ms_shipped = 0 AND SCHEMA_NAME(o.[schema_id]) = 'HangFire' AND o.name = 'Counter' EXEC sp_executesql @dropIndexSql2; PRINT 'Dropped all indexes on the [HangFire].[Counter] table'; -- [Counter].[Id] column can already be added to make replication work as written above, so we will re-create it -- to ensure it is in the expected format. PRINT 'Checking for existence of the [HangFire].[Counter].[Id] column'; IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Counter' AND COLUMN_NAME = 'Id' AND TABLE_SCHEMA='HangFire') BEGIN ALTER TABLE [HangFire].[Counter] DROP COLUMN [Id]; PRINT 'Dropped [HangFire].[Counter].[Id] column'; END ALTER TABLE [HangFire].[Counter] ADD [Id] BIGINT IDENTITY(1, 1); PRINT 'Created [HangFire].[Counter].[Id] column'; ALTER TABLE [HangFire].[Counter] ADD CONSTRAINT [PK_HangFire_Counter] PRIMARY KEY CLUSTERED ( [Key] ASC, [Id] ASC ); PRINT 'Created clustered primary key PK_HangFire_Counter ([Key], [Id])'; -- SqlServerStorageOptions.UseIgnoreDupKeyOption will yield much better results with INSERT/UPDATE operators -- instead of MERGE for [Set] and [Hash] tables. This change is also compatible with older clients, since -- MERGE operator is used there, so those clients are forward-compatible with these changes. ALTER TABLE [HangFire].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON); PRINT 'Enabled IGNORE_DUP_KEY option for [HangFire].[Set] table'; ALTER TABLE [HangFire].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON); PRINT 'Enabled IGNORE_DUP_KEY option for [HangFire].[Hash] table'; ALTER TABLE [HangFire].[JobQueue] DROP CONSTRAINT [PK_HangFire_JobQueue]; PRINT 'Dropped constraint [PK_HangFire_JobQueue] to modify the [HangFire].[JobQueue].[Id] column'; ALTER TABLE [HangFire].[JobQueue] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Changed [HangFire].[JobQueue].[Id] column type to BIGINT'; ALTER TABLE [HangFire].[JobQueue] ADD CONSTRAINT [PK_HangFire_JobQueue] PRIMARY KEY CLUSTERED ( [Queue] ASC, [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_JobQueue]'; SET @CURRENT_SCHEMA_VERSION = 8; END IF @CURRENT_SCHEMA_VERSION = 8 BEGIN PRINT 'Installing schema version 9'; CREATE NONCLUSTERED INDEX [IX_HangFire_State_CreatedAt] ON [HangFire].[State] ([CreatedAt] ASC) SET @CURRENT_SCHEMA_VERSION = 9; END /*IF @CURRENT_SCHEMA_VERSION = 9 BEGIN PRINT 'Installing schema version 10'; Insert migration here SET @CURRENT_SCHEMA_VERSION = 10; END*/ UPDATE [HangFire].[Schema] SET [Version] = @CURRENT_SCHEMA_VERSION IF @@ROWCOUNT = 0 INSERT INTO [HangFire].[Schema] ([Version]) VALUES (@CURRENT_SCHEMA_VERSION) PRINT 'Hangfire database schema installed'; COMMIT TRANSACTION; PRINT 'Hangfire SQL objects installed'; ================================================ FILE: src/Hangfire.SqlServer/DefaultInstall.tt ================================================ <#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ assembly name="$(TargetPath)" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".sql" #> <# var script = GetStringResource( typeof(Hangfire.SqlServer.IPersistentJobQueue).Assembly, "Hangfire.SqlServer.Install.sql"); script = script.Replace("$(HangFireSchema)", "HangFire"); Write(script); #> <#+ private string GetStringResource(System.Reflection.Assembly assembly, string resourceName) { using (var stream = assembly.GetManifestResourceStream(resourceName)) { if (stream == null) { throw new InvalidOperationException(String.Format( "Requested resource `{0}` was not found in the assembly `{1}`.", resourceName, assembly)); } using (var reader = new System.IO.StreamReader(stream)) { return reader.ReadToEnd(); } } } #> ================================================ FILE: src/Hangfire.SqlServer/EnqueuedAndFetchedCountDto.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.SqlServer { public class EnqueuedAndFetchedCountDto { public int? EnqueuedCount { get; set; } public int? FetchedCount { get; set; } } } ================================================ FILE: src/Hangfire.SqlServer/Entities/JobParameter.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.SqlServer.Entities { internal sealed class JobParameter { public long JobId { get; set; } public string Name { get; set; } public string Value { get; set; } } } ================================================ FILE: src/Hangfire.SqlServer/Entities/Server.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.SqlServer.Entities { internal sealed class Server { public string Id { get; set; } public string Data { get; set; } public DateTime LastHeartbeat { get; set; } } } ================================================ FILE: src/Hangfire.SqlServer/Entities/ServerData.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.SqlServer.Entities { internal sealed class ServerData { public int WorkerCount { get; set; } public string[] Queues { get; set; } public DateTime? StartedAt { get; set; } } } ================================================ FILE: src/Hangfire.SqlServer/Entities/SqlHash.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.SqlServer.Entities { internal sealed class SqlHash { public string Key { get; set; } public string Field { get; set; } public string Value { get; set; } public DateTime? ExpireAt { get; set; } } } ================================================ FILE: src/Hangfire.SqlServer/Entities/SqlJob.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.SqlServer.Entities { internal sealed class SqlJob { public long Id { get; set; } public string InvocationData { get; set; } public string Arguments { get; set; } public DateTime CreatedAt { get; set; } public DateTime? ExpireAt { get; set; } public DateTime? FetchedAt { get; set; } public string StateName { get; set; } public string StateReason { get; set; } public string StateData { get; set; } public DateTime? StateChanged { get; set; } } } ================================================ FILE: src/Hangfire.SqlServer/Entities/SqlState.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.SqlServer.Entities { internal sealed class SqlState { public long JobId { get; set; } public string Name { get; set; } public string Reason { get; set; } public DateTime CreatedAt { get; set; } public string Data { get; set; } } } ================================================ FILE: src/Hangfire.SqlServer/ExceptionTypeHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2022 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Reflection; namespace Hangfire.SqlServer { internal static class ExceptionTypeHelper { private static readonly Type OutOfMemoryType = typeof(OutOfMemoryException); #if !NETSTANDARD1_3 private static readonly Type StackOverflowType = typeof(StackOverflowException); private static readonly Type ThreadAbortType = typeof(System.Threading.ThreadAbortException); private static readonly Type AccessViolationType = typeof(AccessViolationException); #endif private static readonly Type SecurityType = typeof(System.Security.SecurityException); internal static bool IsCatchableExceptionType(this Exception e) { var type = e.GetType(); return type != OutOfMemoryType && #if !NETSTANDARD1_3 type != StackOverflowType && type != ThreadAbortType && type != AccessViolationType && #endif !SecurityType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); } } } ================================================ FILE: src/Hangfire.SqlServer/ExpirationManager.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Data; using System.Data.Common; using System.Globalization; using System.Threading; using Hangfire.Common; using Hangfire.Logging; using Hangfire.Server; using Hangfire.Storage; namespace Hangfire.SqlServer { #pragma warning disable 618 internal sealed class ExpirationManager : IServerComponent, IBackgroundProcess #pragma warning restore 618 { private const string DistributedLockKey = "locks:expirationmanager"; private static readonly TimeSpan DefaultLockTimeout = TimeSpan.FromMinutes(5); // This value should be high enough to optimize the deletion as much, as possible, // reducing the number of queries. But low enough to cause lock escalations (it // appears, when ~5000 locks were taken, but this number is a subject of version). // Note, that lock escalation may also happen during the cascade deletions for // State (3-5 rows/job usually) and JobParameters (2-3 rows/job usually) tables. private const int DefaultNumberOfRecordsInSinglePass = 1000; private static readonly string[] ProcessedTables = { "AggregatedCounter", "Job", "List", "Set", "Hash", }; private readonly ILog _logger = LogProvider.For(); private readonly SqlServerStorage _storage; private readonly TimeSpan _stateExpirationTimeout; private readonly TimeSpan _checkInterval; public ExpirationManager(SqlServerStorage storage, TimeSpan stateExpirationTimeout, TimeSpan checkInterval) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (stateExpirationTimeout < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(stateExpirationTimeout), "Timeout value should be equal to or greater than zero."); if (checkInterval <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(checkInterval), "Timeout value should be greater than zero."); _storage = storage; _stateExpirationTimeout = stateExpirationTimeout; _checkInterval = checkInterval; } public void Execute(CancellationToken cancellationToken) { ExecuteCore(_storage, cancellationToken); } public void Execute(BackgroundProcessContext context) { if (context.Storage is not SqlServerStorage storage) { return; } ExecuteCore(storage, context.StoppingToken); } private void ExecuteCore(SqlServerStorage storage, CancellationToken cancellationToken) { var numberOfRecordsInSinglePass = storage.Options.DeleteExpiredBatchSize; if (numberOfRecordsInSinglePass <= 0 || numberOfRecordsInSinglePass > 100_000) { numberOfRecordsInSinglePass = DefaultNumberOfRecordsInSinglePass; } foreach (var table in ProcessedTables) { try { CleanupTable(storage, GetExpireQuery(storage, table), table, numberOfRecordsInSinglePass, cancellationToken); } catch (DbException ex) { _logger.ErrorException($"Error occurred while cleaning up the '{table}' table: {ex.Message}", ex); } } if (_stateExpirationTimeout > TimeSpan.Zero) { try { CleanupTable(storage, GetStateCleanupQuery(storage), "State", numberOfRecordsInSinglePass, cancellationToken, command => command.AddParameter("@expireMin", (long)_stateExpirationTimeout.Negate().TotalMinutes, DbType.Int64)); } catch (DbException ex) { _logger.ErrorException($"Error occurred while cleaning up the 'State' table: {ex.Message}", ex); } } cancellationToken.Wait(_checkInterval); } public override string ToString() { return GetType().ToString(); } private void CleanupTable(SqlServerStorage storage, string query, string table, int numberOfRecordsInSinglePass, CancellationToken cancellationToken, Action additionalActions = null) { _logger.Debug($"Removing outdated records from the '{table}' table..."); UseConnectionDistributedLock(storage, connection => { int affected; do { affected = ExecuteNonQuery( connection, query, numberOfRecordsInSinglePass, additionalActions, cancellationToken); } while (affected == numberOfRecordsInSinglePass); return affected; }); _logger.Trace($"Outdated records removed from the '{table}' table."); } private T UseConnectionDistributedLock(SqlServerStorage storage, Func action) { try { return storage.UseConnection(null, static (_, connection, ctx) => { SqlServerDistributedLock.Acquire(connection, DistributedLockKey, DefaultLockTimeout); try { return ctx(connection); } finally { SqlServerDistributedLock.Release(connection, DistributedLockKey); } }, action); } catch (DistributedLockTimeoutException e) when (e.Resource == DistributedLockKey) { // DistributedLockTimeoutException here doesn't mean that outdated records weren't removed. // It just means another Hangfire server did this work. _logger.Log( LogLevel.Debug, () => $@"An exception was thrown during acquiring distributed lock on the {DistributedLockKey} resource within {DefaultLockTimeout.TotalSeconds} seconds. Outdated records were not removed. It will be retried in {_checkInterval.TotalSeconds} seconds.", e); return default; } } private static string GetExpireQuery(SqlServerStorage storage, string table) { if (table.Equals("AggregatedCounter", StringComparison.OrdinalIgnoreCase)) { // Schema 5, which still should be supported by Hangfire doesn't have an index that covers // the `ExpireAt` column, making it impossible to run the query. return String.Format(CultureInfo.InvariantCulture, storage.GetQueryFromTemplate(static schemaName => $@" set deadlock_priority low; set transaction isolation level read committed; set xact_abort on; set lock_timeout 1000; delete top (@count) T from [{schemaName}].[{{0}}] T where ExpireAt < @now option (loop join, optimize for (@count = 20000));"), table); } return String.Format(CultureInfo.InvariantCulture, storage.GetQueryFromTemplate(static schemaName => $@" set deadlock_priority low; set transaction isolation level read committed; set xact_abort on; set lock_timeout 1000; delete top (@count) T from [{schemaName}].[{{0}}] T with (forceseek) where ExpireAt < @now option (loop join, optimize for (@count = 20000));"), table); } private static string GetStateCleanupQuery(SqlServerStorage storage) { // TODO: Make expiration condition configurable return storage.GetQueryFromTemplate(static schemaName => $@" set deadlock_priority low; set transaction isolation level read committed; set xact_abort on; set lock_timeout 1000; ;with cte as ( select s.[JobId], s.[Id] from [{schemaName}].[State] s with (forceseek) where s.[CreatedAt] < dateadd(minute, @expireMin, @now) and exists ( select * from [{schemaName}].[Job] j with (forceseek) where j.[Id] = s.[JobId] and j.[StateId] != s.[Id])) delete top(@count) from cte option (maxdop 1);"); } private static int ExecuteNonQuery( DbConnection connection, string commandText, int numberOfRecordsInSinglePass, Action additionalActions, CancellationToken cancellationToken) { using var command = connection.Create(commandText, timeout: 0) .AddParameter("@count", numberOfRecordsInSinglePass, DbType.Int32) .AddParameter("@now", DateTime.UtcNow, DbType.DateTime); additionalActions?.Invoke(command); using (cancellationToken.Register(static state => ((DbCommand)state).Cancel(), command)) { try { return command.ExecuteNonQuery(); } catch (DbException ex) when (cancellationToken.IsCancellationRequested || ex.Message.Contains("Lock request time out period exceeded")) { // Exception was triggered due to the Cancel method call, ignoring return 0; } } } } } ================================================ FILE: src/Hangfire.SqlServer/Hangfire.SqlServer.csproj ================================================  net451;netstandard1.3;netstandard2.0; true Hangfire.SqlServer $(DefineConstants);FEATURE_TRANSACTIONSCOPE;FEATURE_CONFIGURATIONMANAGER True True DefaultInstall.tt TextTemplatingFileGenerator DefaultInstall.sql ================================================ FILE: src/Hangfire.SqlServer/IPersistentJobQueue.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Diagnostics.CodeAnalysis; using System.Threading; using Hangfire.Storage; namespace Hangfire.SqlServer { [SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "This interface represents a persistent queue by design.")] public interface IPersistentJobQueue { IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken); #if FEATURE_TRANSACTIONSCOPE void Enqueue(System.Data.IDbConnection connection, string queue, string jobId); #else void Enqueue( System.Data.Common.DbConnection connection, System.Data.Common.DbTransaction transaction, string queue, string jobId); #endif } } ================================================ FILE: src/Hangfire.SqlServer/IPersistentJobQueueMonitoringApi.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; namespace Hangfire.SqlServer { public interface IPersistentJobQueueMonitoringApi { IEnumerable GetQueues(); IEnumerable GetEnqueuedJobIds(string queue, int from, int perPage); // TODO: Extend return type by including DateTime to allow getting the FetchedAt value in 2.0 IEnumerable GetFetchedJobIds(string queue, int from, int perPage); EnqueuedAndFetchedCountDto GetEnqueuedAndFetchedCount(string queue); } } ================================================ FILE: src/Hangfire.SqlServer/IPersistentJobQueueProvider.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.SqlServer { public interface IPersistentJobQueueProvider { IPersistentJobQueue GetJobQueue(); IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi(); } } ================================================ FILE: src/Hangfire.SqlServer/Install.sql ================================================ -- This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. -- -- Hangfire is free software: you can redistribute it and/or modify -- it under the terms of the GNU Lesser General Public License as -- published by the Free Software Foundation, either version 3 -- of the License, or any later version. -- -- Hangfire is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU Lesser General Public License for more details. -- -- You should have received a copy of the GNU Lesser General Public -- License along with Hangfire. If not, see . SET NOCOUNT ON SET XACT_ABORT ON DECLARE @TARGET_SCHEMA_VERSION INT; DECLARE @DISABLE_HEAVY_MIGRATIONS BIT; SET @TARGET_SCHEMA_VERSION = 9; --SET @DISABLE_HEAVY_MIGRATIONS = 1; PRINT 'Installing Hangfire SQL objects...'; BEGIN TRANSACTION; -- Acquire exclusive lock to prevent deadlocks caused by schema creation / version update DECLARE @SchemaLockResult INT; EXEC @SchemaLockResult = sp_getapplock @Resource = '$(HangFireSchema):SchemaLock', @LockMode = 'Exclusive' -- Create the database schema if it doesn't exists IF NOT EXISTS (SELECT [schema_id] FROM [sys].[schemas] WHERE [name] = '$(HangFireSchema)') BEGIN EXEC (N'CREATE SCHEMA [$(HangFireSchema)]'); PRINT 'Created database schema [$(HangFireSchema)]'; END ELSE PRINT 'Database schema [$(HangFireSchema)] already exists'; DECLARE @SCHEMA_ID int; SELECT @SCHEMA_ID = [schema_id] FROM [sys].[schemas] WHERE [name] = '$(HangFireSchema)'; -- Create the [$(HangFireSchema)].Schema table if not exists IF NOT EXISTS(SELECT [object_id] FROM [sys].[tables] WHERE [name] = 'Schema' AND [schema_id] = @SCHEMA_ID) BEGIN CREATE TABLE [$(HangFireSchema)].[Schema]( [Version] [int] NOT NULL, CONSTRAINT [PK_HangFire_Schema] PRIMARY KEY CLUSTERED ([Version] ASC) ); PRINT 'Created table [$(HangFireSchema)].[Schema]'; END ELSE PRINT 'Table [$(HangFireSchema)].[Schema] already exists'; DECLARE @CURRENT_SCHEMA_VERSION int; SELECT @CURRENT_SCHEMA_VERSION = [Version] FROM [$(HangFireSchema)].[Schema]; PRINT 'Current Hangfire schema version: ' + CASE WHEN @CURRENT_SCHEMA_VERSION IS NULL THEN 'none' ELSE CONVERT(nvarchar, @CURRENT_SCHEMA_VERSION) END; IF @CURRENT_SCHEMA_VERSION IS NOT NULL AND @CURRENT_SCHEMA_VERSION > @TARGET_SCHEMA_VERSION BEGIN ROLLBACK TRANSACTION; PRINT 'Hangfire current database schema version ' + CAST(@CURRENT_SCHEMA_VERSION AS NVARCHAR) + ' is newer than the configured SqlServerStorage schema version ' + CAST(@TARGET_SCHEMA_VERSION AS NVARCHAR) + '. Will not apply any migrations.'; RETURN; END -- Install [$(HangFireSchema)] schema objects IF @CURRENT_SCHEMA_VERSION IS NULL BEGIN IF @DISABLE_HEAVY_MIGRATIONS = 1 BEGIN SET @DISABLE_HEAVY_MIGRATIONS = 0; PRINT 'Enabling HEAVY_MIGRATIONS, because we are installing objects from scratch'; END PRINT 'Installing schema version 1'; -- Create job tables CREATE TABLE [$(HangFireSchema)].[Job] ( [Id] [int] IDENTITY(1,1) NOT NULL, [StateId] [int] NULL, [StateName] [nvarchar](20) NULL, -- To speed-up queries. [InvocationData] [nvarchar](max) NOT NULL, [Arguments] [nvarchar](max) NOT NULL, [CreatedAt] [datetime] NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Job] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[Job]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Job_StateName] ON [$(HangFireSchema)].[Job] ([StateName] ASC); PRINT 'Created index [IX_HangFire_Job_StateName]'; -- Job history table CREATE TABLE [$(HangFireSchema)].[State] ( [Id] [int] IDENTITY(1,1) NOT NULL, [JobId] [int] NOT NULL, [Name] [nvarchar](20) NOT NULL, [Reason] [nvarchar](100) NULL, [CreatedAt] [datetime] NOT NULL, [Data] [nvarchar](max) NULL, CONSTRAINT [PK_HangFire_State] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[State]'; ALTER TABLE [$(HangFireSchema)].[State] ADD CONSTRAINT [FK_HangFire_State_Job] FOREIGN KEY([JobId]) REFERENCES [$(HangFireSchema)].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Created constraint [FK_HangFire_State_Job]'; CREATE NONCLUSTERED INDEX [IX_HangFire_State_JobId] ON [$(HangFireSchema)].[State] ([JobId] ASC); PRINT 'Created index [IX_HangFire_State_JobId]'; -- Job parameters table CREATE TABLE [$(HangFireSchema)].[JobParameter]( [Id] [int] IDENTITY(1,1) NOT NULL, [JobId] [int] NOT NULL, [Name] [nvarchar](40) NOT NULL, [Value] [nvarchar](max) NULL, CONSTRAINT [PK_HangFire_JobParameter] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[JobParameter]'; ALTER TABLE [$(HangFireSchema)].[JobParameter] ADD CONSTRAINT [FK_HangFire_JobParameter_Job] FOREIGN KEY([JobId]) REFERENCES [$(HangFireSchema)].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Created constraint [FK_HangFire_JobParameter_Job]'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobParameter_JobIdAndName] ON [$(HangFireSchema)].[JobParameter] ( [JobId] ASC, [Name] ASC ); PRINT 'Created index [IX_HangFire_JobParameter_JobIdAndName]'; -- Job queue table CREATE TABLE [$(HangFireSchema)].[JobQueue]( [Id] [int] IDENTITY(1,1) NOT NULL, [JobId] [int] NOT NULL, [Queue] [nvarchar](20) NOT NULL, [FetchedAt] [datetime] NULL, CONSTRAINT [PK_HangFire_JobQueue] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[JobQueue]'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobQueue_JobIdAndQueue] ON [$(HangFireSchema)].[JobQueue] ( [JobId] ASC, [Queue] ASC ); PRINT 'Created index [IX_HangFire_JobQueue_JobIdAndQueue]'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [$(HangFireSchema)].[JobQueue] ( [Queue] ASC, [FetchedAt] ASC ); PRINT 'Created index [IX_HangFire_JobQueue_QueueAndFetchedAt]'; -- Servers table CREATE TABLE [$(HangFireSchema)].[Server]( [Id] [nvarchar](200) NOT NULL, [Data] [nvarchar](max) NULL, [LastHeartbeat] [datetime] NULL, CONSTRAINT [PK_HangFire_Server] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[Server]'; -- Extension tables CREATE TABLE [$(HangFireSchema)].[Hash]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Name] [nvarchar](40) NOT NULL, [StringValue] [nvarchar](max) NULL, [IntValue] [int] NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Hash] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[Hash]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Hash_KeyAndName] ON [$(HangFireSchema)].[Hash] ( [Key] ASC, [Name] ASC ); PRINT 'Created index [UX_HangFire_Hash_KeyAndName]'; CREATE TABLE [$(HangFireSchema)].[List]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Value] [nvarchar](max) NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_List] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[List]'; CREATE TABLE [$(HangFireSchema)].[Set]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Score] [float] NOT NULL, [Value] [nvarchar](256) NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Set] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[Set]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Set_KeyAndValue] ON [$(HangFireSchema)].[Set] ( [Key] ASC, [Value] ASC ); PRINT 'Created index [UX_HangFire_Set_KeyAndValue]'; CREATE TABLE [$(HangFireSchema)].[Value]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [StringValue] [nvarchar](max) NULL, [IntValue] [int] NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Value] PRIMARY KEY CLUSTERED ( [Id] ASC ) ); PRINT 'Created table [$(HangFireSchema)].[Value]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Value_Key] ON [$(HangFireSchema)].[Value] ( [Key] ASC ); PRINT 'Created index [UX_HangFire_Value_Key]'; CREATE TABLE [$(HangFireSchema)].[Counter]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Value] [tinyint] NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_Counter] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[Counter]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Counter_Key] ON [$(HangFireSchema)].[Counter] ([Key] ASC) INCLUDE ([Value]); PRINT 'Created index [IX_HangFire_Counter_Key]'; SET @CURRENT_SCHEMA_VERSION = 1; END IF @CURRENT_SCHEMA_VERSION = 1 BEGIN PRINT 'Installing schema version 2'; -- https://github.com/odinserj/HangFire/issues/83 DROP INDEX [IX_HangFire_Counter_Key] ON [$(HangFireSchema)].[Counter]; ALTER TABLE [$(HangFireSchema)].[Counter] ALTER COLUMN [Value] SMALLINT NOT NULL; CREATE NONCLUSTERED INDEX [IX_HangFire_Counter_Key] ON [$(HangFireSchema)].[Counter] ([Key] ASC) INCLUDE ([Value]); PRINT 'Index [IX_HangFire_Counter_Key] re-created'; DROP TABLE [$(HangFireSchema)].[Value]; DROP TABLE [$(HangFireSchema)].[Hash]; PRINT 'Dropped tables [$(HangFireSchema)].[Value] and [$(HangFireSchema)].[Hash]' DELETE FROM [$(HangFireSchema)].[Server] WHERE [LastHeartbeat] IS NULL; ALTER TABLE [$(HangFireSchema)].[Server] ALTER COLUMN [LastHeartbeat] DATETIME NOT NULL; SET @CURRENT_SCHEMA_VERSION = 2; END IF @CURRENT_SCHEMA_VERSION = 2 BEGIN PRINT 'Installing schema version 3'; DROP INDEX [IX_HangFire_JobQueue_JobIdAndQueue] ON [$(HangFireSchema)].[JobQueue]; PRINT 'Dropped index [IX_HangFire_JobQueue_JobIdAndQueue]'; CREATE TABLE [$(HangFireSchema)].[Hash]( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Field] [nvarchar](100) NOT NULL, [Value] [nvarchar](max) NULL, [ExpireAt] [datetime2](7) NULL, CONSTRAINT [PK_HangFire_Hash] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[Hash]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_Hash_Key_Field] ON [$(HangFireSchema)].[Hash] ( [Key] ASC, [Field] ASC ); PRINT 'Created index [UX_HangFire_Hash_Key_Field]'; SET @CURRENT_SCHEMA_VERSION = 3; END IF @CURRENT_SCHEMA_VERSION = 3 BEGIN PRINT 'Installing schema version 4'; CREATE TABLE [$(HangFireSchema)].[AggregatedCounter] ( [Id] [int] IDENTITY(1,1) NOT NULL, [Key] [nvarchar](100) NOT NULL, [Value] [bigint] NOT NULL, [ExpireAt] [datetime] NULL, CONSTRAINT [PK_HangFire_CounterAggregated] PRIMARY KEY CLUSTERED ([Id] ASC) ); PRINT 'Created table [$(HangFireSchema)].[AggregatedCounter]'; CREATE UNIQUE NONCLUSTERED INDEX [UX_HangFire_CounterAggregated_Key] ON [$(HangFireSchema)].[AggregatedCounter] ( [Key] ASC ) INCLUDE ([Value]); PRINT 'Created index [UX_HangFire_CounterAggregated_Key]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Hash_ExpireAt] ON [$(HangFireSchema)].[Hash] ([ExpireAt]) INCLUDE ([Id]); CREATE NONCLUSTERED INDEX [IX_HangFire_Job_ExpireAt] ON [$(HangFireSchema)].[Job] ([ExpireAt]) INCLUDE ([Id]); CREATE NONCLUSTERED INDEX [IX_HangFire_List_ExpireAt] ON [$(HangFireSchema)].[List] ([ExpireAt]) INCLUDE ([Id]); CREATE NONCLUSTERED INDEX [IX_HangFire_Set_ExpireAt] ON [$(HangFireSchema)].[Set] ([ExpireAt]) INCLUDE ([Id]); PRINT 'Created indexes for [ExpireAt] columns'; CREATE NONCLUSTERED INDEX [IX_HangFire_Hash_Key] ON [$(HangFireSchema)].[Hash] ([Key] ASC) INCLUDE ([ExpireAt]); PRINT 'Created index [IX_HangFire_Hash_Key]'; CREATE NONCLUSTERED INDEX [IX_HangFire_List_Key] ON [$(HangFireSchema)].[List] ([Key] ASC) INCLUDE ([ExpireAt], [Value]); PRINT 'Created index [IX_HangFire_List_Key]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_Key] ON [$(HangFireSchema)].[Set] ([Key] ASC) INCLUDE ([ExpireAt], [Value]); PRINT 'Created index [IX_HangFire_Set_Key]'; SET @CURRENT_SCHEMA_VERSION = 4; END IF @CURRENT_SCHEMA_VERSION = 4 BEGIN PRINT 'Installing schema version 5'; DROP INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [$(HangFireSchema)].[JobQueue]; PRINT 'Dropped index [IX_HangFire_JobQueue_QueueAndFetchedAt] to modify the [$(HangFireSchema)].[JobQueue].[Queue] column'; ALTER TABLE [$(HangFireSchema)].[JobQueue] ALTER COLUMN [Queue] NVARCHAR (50) NOT NULL; PRINT 'Modified [$(HangFireSchema)].[JobQueue].[Queue] length to 50'; CREATE NONCLUSTERED INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [$(HangFireSchema)].[JobQueue] ( [Queue] ASC, [FetchedAt] ASC ); PRINT 'Re-created index [IX_HangFire_JobQueue_QueueAndFetchedAt]'; ALTER TABLE [$(HangFireSchema)].[Server] DROP CONSTRAINT [PK_HangFire_Server] PRINT 'Dropped constraint [PK_HangFire_Server] to modify the [HangFire].[Server].[Id] column'; ALTER TABLE [$(HangFireSchema)].[Server] ALTER COLUMN [Id] NVARCHAR (200) NOT NULL; PRINT 'Modified [$(HangFireSchema)].[Server].[Id] length to 200'; ALTER TABLE [$(HangFireSchema)].[Server] ADD CONSTRAINT [PK_HangFire_Server] PRIMARY KEY CLUSTERED ( [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_Server]'; SET @CURRENT_SCHEMA_VERSION = 5; END IF @CURRENT_SCHEMA_VERSION = 5 AND @DISABLE_HEAVY_MIGRATIONS = 1 BEGIN PRINT 'Migration process STOPPED at schema version ' + CAST(@CURRENT_SCHEMA_VERSION AS NVARCHAR) + '. WILL NOT upgrade to schema version ' + CAST(@TARGET_SCHEMA_VERSION AS NVARCHAR) + ', because @DISABLE_HEAVY_MIGRATIONS option is set.'; END ELSE IF @CURRENT_SCHEMA_VERSION = 5 BEGIN PRINT 'Installing schema version 6'; -- First, we will drop all the secondary indexes on the HangFire.Set table, because we will -- modify that table, and unknown indexes may be added there (see https://github.com/HangfireIO/Hangfire/issues/844). -- So, we'll drop all of them, and then re-create the required index with a well-known name. DECLARE @dropIndexSql NVARCHAR(MAX) = N''; SELECT @dropIndexSql += N'DROP INDEX ' + QUOTENAME(SCHEMA_NAME(o.[schema_id])) + '.' + QUOTENAME(o.name) + '.' + QUOTENAME(i.name) + ';' FROM sys.indexes AS i INNER JOIN sys.tables AS o ON i.[object_id] = o.[object_id] WHERE i.is_primary_key = 0 AND i.index_id <> 0 AND o.is_ms_shipped = 0 AND SCHEMA_NAME(o.[schema_id]) = '$(HangFireSchema)' AND o.name = 'Set'; EXEC sp_executesql @dropIndexSql; PRINT 'Dropped all secondary indexes on the [Set] table'; -- Next, we'll remove the unnecessary indexes. They were unnecessary in the previous schema, -- and are unnecessary in the new schema as well. We'll not re-create them. DROP INDEX [IX_HangFire_Hash_Key] ON [$(HangFireSchema)].[Hash]; PRINT 'Dropped unnecessary index [IX_HangFire_Hash_Key]'; -- Next, all the indexes that cover expiration will be filtered, to include only non-null values. This -- will prevent unnecessary index modifications – we are seeking these indexes only for non-null -- expiration time. Also, they include the Id column by a mistake. So we'll re-create them later in the -- migration. DROP INDEX [IX_HangFire_Hash_ExpireAt] ON [$(HangFireSchema)].[Hash]; PRINT 'Dropped index [IX_HangFire_Hash_ExpireAt]'; DROP INDEX [IX_HangFire_Job_ExpireAt] ON [$(HangFireSchema)].[Job]; PRINT 'Dropped index [IX_HangFire_Job_ExpireAt]'; DROP INDEX [IX_HangFire_List_ExpireAt] ON [$(HangFireSchema)].[List]; PRINT 'Dropped index [IX_HangFire_List_ExpireAt]'; -- IX_HangFire_Job_StateName index can also be optimized, since we are querying it only with a -- non-null state name. This will decrease the number of operations, when creating a background job. -- It will be recreated later in the migration. DROP INDEX [IX_HangFire_Job_StateName] ON [$(HangFireSchema)].Job; PRINT 'Dropped index [IX_HangFire_Job_StateName]'; -- Dropping foreign key constraints based on the JobId column, because we need to modify the underlying -- column type of the clustered index to BIGINT. We'll recreate them later in the migration. ALTER TABLE [$(HangFireSchema)].[JobParameter] DROP CONSTRAINT [FK_HangFire_JobParameter_Job]; PRINT 'Dropped constraint [FK_HangFire_JobParameter_Job]'; ALTER TABLE [$(HangFireSchema)].[State] DROP CONSTRAINT [FK_HangFire_State_Job]; PRINT 'Dropped constraint [FK_HangFire_State_Job]'; -- We are going to create composite clustered indexes that are more natural for the following tables, -- so the following indexes will be unnecessary. Natural sorting will keep related data close to each -- other, and simplify the index modifications by the cost of fragmentation and additional page splits. DROP INDEX [UX_HangFire_CounterAggregated_Key] ON [$(HangFireSchema)].[AggregatedCounter]; PRINT 'Dropped index [UX_HangFire_CounterAggregated_Key]'; DROP INDEX [IX_HangFire_Counter_Key] ON [$(HangFireSchema)].[Counter]; PRINT 'Dropped index [IX_HangFire_Counter_Key]'; DROP INDEX [IX_HangFire_JobParameter_JobIdAndName] ON [$(HangFireSchema)].[JobParameter]; PRINT 'Dropped index [IX_HangFire_JobParameter_JobIdAndName]'; DROP INDEX [IX_HangFire_JobQueue_QueueAndFetchedAt] ON [$(HangFireSchema)].[JobQueue]; PRINT 'Dropped index [IX_HangFire_JobQueue_QueueAndFetchedAt]'; DROP INDEX [UX_HangFire_Hash_Key_Field] ON [$(HangFireSchema)].[Hash]; PRINT 'Dropped index [UX_HangFire_Hash_Key_Field]'; DROP INDEX [IX_HangFire_List_Key] ON [$(HangFireSchema)].[List]; PRINT 'Dropped index [IX_HangFire_List_Key]'; DROP INDEX [IX_HangFire_State_JobId] ON [$(HangFireSchema)].[State]; PRINT 'Dropped index [IX_HangFire_State_JobId]'; -- Then, we need to drop the primary key constraints, to modify id columns to the BIGINT type. Some of them -- will be re-created later in the migration. But some of them would be removed forever, because their -- uniqueness property sometimes unnecessary. ALTER TABLE [$(HangFireSchema)].[AggregatedCounter] DROP CONSTRAINT [PK_HangFire_CounterAggregated]; PRINT 'Dropped constraint [PK_HangFire_CounterAggregated]'; ALTER TABLE [$(HangFireSchema)].[Counter] DROP CONSTRAINT [PK_HangFire_Counter]; PRINT 'Dropped constraint [PK_HangFire_Counter]'; ALTER TABLE [$(HangFireSchema)].[Hash] DROP CONSTRAINT [PK_HangFire_Hash]; PRINT 'Dropped constraint [PK_HangFire_Hash]'; ALTER TABLE [$(HangFireSchema)].[Job] DROP CONSTRAINT [PK_HangFire_Job]; PRINT 'Dropped constraint [PK_HangFire_Job]'; ALTER TABLE [$(HangFireSchema)].[JobParameter] DROP CONSTRAINT [PK_HangFire_JobParameter]; PRINT 'Dropped constraint [PK_HangFire_JobParameter]'; ALTER TABLE [$(HangFireSchema)].[JobQueue] DROP CONSTRAINT [PK_HangFire_JobQueue]; PRINT 'Dropped constraint [PK_HangFire_JobQueue]'; ALTER TABLE [$(HangFireSchema)].[List] DROP CONSTRAINT [PK_HangFire_List]; PRINT 'Dropped constraint [PK_HangFire_List]'; ALTER TABLE [$(HangFireSchema)].[Set] DROP CONSTRAINT [PK_HangFire_Set]; PRINT 'Dropped constraint [PK_HangFire_Set]'; ALTER TABLE [$(HangFireSchema)].[State] DROP CONSTRAINT [PK_HangFire_State]; PRINT 'Dropped constraint [PK_HangFire_State]'; -- We are removing identity columns of the following tables completely, their clustered -- index will be based on natural values. So, instead of modifying them to BIGINT, we -- are dropping them. ALTER TABLE [$(HangFireSchema)].[AggregatedCounter] DROP COLUMN [Id]; PRINT 'Dropped [AggregatedCounter].[Id] column, we will cluster on [Key] column with uniqufier'; ALTER TABLE [$(HangFireSchema)].[Counter] DROP COLUMN [Id]; PRINT 'Dropped [Counter].[Id] column, we will cluster on [Key] column'; ALTER TABLE [$(HangFireSchema)].[Hash] DROP COLUMN [Id]; PRINT 'Dropped [Hash].[Id] column, we will cluster on [Key]/[Field] columns'; ALTER TABLE [$(HangFireSchema)].[Set] DROP COLUMN [Id]; PRINT 'Dropped [Set].[Id] column, we will cluster on [Key]/[Value] columns'; ALTER TABLE [$(HangFireSchema)].[JobParameter] DROP COLUMN [Id]; PRINT 'Dropped [JobParameter].[Id] column, we will cluster on [JobId]/[Name] columns'; -- Then we need to modify all the remaining Id columns to be of type BIGINT. ALTER TABLE [$(HangFireSchema)].[List] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [List].[Id] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[Job] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [Job].[Id] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[Job] ALTER COLUMN [StateId] BIGINT NULL; PRINT 'Modified [Job].[StateId] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[JobParameter] ALTER COLUMN [JobId] BIGINT NOT NULL; PRINT 'Modified [JobParameter].[JobId] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[JobQueue] ALTER COLUMN [JobId] BIGINT NOT NULL; PRINT 'Modified [JobQueue].[JobId] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[JobQueue] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [JobQueue].[Id] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[State] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Modified [State].[Id] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[State] ALTER COLUMN [JobId] BIGINT NOT NULL; PRINT 'Modified [State].[JobId] type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[Counter] ALTER COLUMN [Value] INT NOT NULL; PRINT 'Modified [Counter].[Value] type to INT'; -- Adding back all the Primary Key constraints or clustered indexes where PKs aren't appropriate. ALTER TABLE [$(HangFireSchema)].[AggregatedCounter] ADD CONSTRAINT [PK_HangFire_CounterAggregated] PRIMARY KEY CLUSTERED ( [Key] ASC ); PRINT 'Re-created constraint [PK_HangFire_CounterAggregated]'; CREATE CLUSTERED INDEX [CX_HangFire_Counter] ON [$(HangFireSchema)].[Counter] ([Key]); PRINT 'Created clustered index [CX_HangFire_Counter]'; ALTER TABLE [$(HangFireSchema)].[Hash] ADD CONSTRAINT [PK_HangFire_Hash] PRIMARY KEY CLUSTERED ( [Key] ASC, [Field] ASC ); PRINT 'Re-created constraint [PK_HangFire_Hash]'; ALTER TABLE [$(HangFireSchema)].[Job] ADD CONSTRAINT [PK_HangFire_Job] PRIMARY KEY CLUSTERED ([Id] ASC); PRINT 'Re-created constraint [PK_HangFire_Job]'; ALTER TABLE [$(HangFireSchema)].[JobParameter] ADD CONSTRAINT [PK_HangFire_JobParameter] PRIMARY KEY CLUSTERED ( [JobId] ASC, [Name] ASC ); PRINT 'Re-created constraint [PK_HangFire_JobParameter]'; ALTER TABLE [$(HangFireSchema)].[JobQueue] ADD CONSTRAINT [PK_HangFire_JobQueue] PRIMARY KEY CLUSTERED ( [Queue] ASC, [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_JobQueue]'; ALTER TABLE [$(HangFireSchema)].[List] ADD CONSTRAINT [PK_HangFire_List] PRIMARY KEY CLUSTERED ( [Key] ASC, [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_List]'; ALTER TABLE [$(HangFireSchema)].[Set] ADD CONSTRAINT [PK_HangFire_Set] PRIMARY KEY CLUSTERED ( [Key] ASC, [Value] ASC ); PRINT 'Re-created constraint [PK_HangFire_Set]'; ALTER TABLE [$(HangFireSchema)].[State] ADD CONSTRAINT [PK_HangFire_State] PRIMARY KEY CLUSTERED ( [JobId] ASC, [Id] ); PRINT 'Re-created constraint [PK_HangFire_State]'; -- Creating secondary, nonclustered indexes CREATE NONCLUSTERED INDEX [IX_HangFire_Job_StateName] ON [$(HangFireSchema)].[Job] ([StateName]) WHERE [StateName] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Job_StateName]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_Score] ON [$(HangFireSchema)].[Set] ([Score]) WHERE [Score] IS NOT NULL; PRINT 'Created index [IX_HangFire_Set_Score]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Server_LastHeartbeat] ON [$(HangFireSchema)].[Server] ([LastHeartbeat]); PRINT 'Created index [IX_HangFire_Server_LastHeartbeat]'; -- Creating filtered indexes for ExpireAt columns CREATE NONCLUSTERED INDEX [IX_HangFire_AggregatedCounter_ExpireAt] ON [$(HangFireSchema)].[AggregatedCounter] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Created index [IX_HangFire_AggregatedCounter_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Hash_ExpireAt] ON [$(HangFireSchema)].[Hash] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Hash_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Job_ExpireAt] ON [$(HangFireSchema)].[Job] ([ExpireAt]) INCLUDE ([StateName]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Job_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_List_ExpireAt] ON [$(HangFireSchema)].[List] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_List_ExpireAt]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_ExpireAt] ON [$(HangFireSchema)].[Set] ([ExpireAt]) WHERE [ExpireAt] IS NOT NULL; PRINT 'Re-created index [IX_HangFire_Set_ExpireAt]'; -- Restoring foreign keys ALTER TABLE [$(HangFireSchema)].[State] ADD CONSTRAINT [FK_HangFire_State_Job] FOREIGN KEY([JobId]) REFERENCES [$(HangFireSchema)].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Re-created constraint [FK_HangFire_State_Job]'; ALTER TABLE [$(HangFireSchema)].[JobParameter] ADD CONSTRAINT [FK_HangFire_JobParameter_Job] FOREIGN KEY([JobId]) REFERENCES [$(HangFireSchema)].[Job] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE; PRINT 'Re-created constraint [FK_HangFire_JobParameter_Job]'; SET @CURRENT_SCHEMA_VERSION = 6; END IF @CURRENT_SCHEMA_VERSION = 6 BEGIN PRINT 'Installing schema version 7'; DROP INDEX [IX_HangFire_Set_Score] ON [$(HangFireSchema)].[Set]; PRINT 'Dropped index [IX_HangFire_Set_Score]'; CREATE NONCLUSTERED INDEX [IX_HangFire_Set_Score] ON [$(HangFireSchema)].[Set] ([Key], [Score]); PRINT 'Created index [IX_HangFire_Set_Score] with the proper composite key'; SET @CURRENT_SCHEMA_VERSION = 7; END IF @CURRENT_SCHEMA_VERSION = 7 AND @DISABLE_HEAVY_MIGRATIONS = 1 BEGIN PRINT 'Migration process STOPPED at schema version ' + CAST(@CURRENT_SCHEMA_VERSION AS NVARCHAR) + '. WILL NOT upgrade to schema version ' + CAST(@TARGET_SCHEMA_VERSION AS NVARCHAR) + ', because @DISABLE_HEAVY_MIGRATIONS option is set.'; END ELSE IF @CURRENT_SCHEMA_VERSION = 7 BEGIN PRINT 'Installing schema version 8'; ALTER TABLE [$(HangFireSchema)].[Server] DROP CONSTRAINT [PK_HangFire_Server] PRINT 'Dropped constraint [PK_HangFire_Server] to modify the [HangFire].[Server].[Id] column'; ALTER TABLE [$(HangFireSchema)].[Server] ALTER COLUMN [Id] NVARCHAR (200) NOT NULL; PRINT 'Modified [$(HangFireSchema)].[Server].[Id] length to 200'; ALTER TABLE [$(HangFireSchema)].[Server] ADD CONSTRAINT [PK_HangFire_Server] PRIMARY KEY CLUSTERED ([Id] ASC); PRINT 'Re-created constraint [PK_HangFire_Server]'; -- Nothing complicated - we just collecting all the secondary indexes and primary key names to delete them. -- We should expect nothing here, because custom columns and indexes can be applied for the [Counter] table -- to make replication work on Microsoft Azure, like in the issue below. -- https://github.com/HangfireIO/Hangfire/issues/1500 DECLARE @dropIndexSql2 NVARCHAR(MAX) = N''; SELECT @dropIndexSql2 += N'DROP INDEX ' + QUOTENAME(SCHEMA_NAME(o.[schema_id])) + '.' + QUOTENAME(o.name) + '.' + QUOTENAME(i.name) + ';' FROM sys.indexes AS i INNER JOIN sys.tables AS o ON i.[object_id] = o.[object_id] WHERE i.is_primary_key = 0 AND i.index_id <> 0 AND o.is_ms_shipped = 0 AND SCHEMA_NAME(o.[schema_id]) = '$(HangFireSchema)' AND o.name = 'Counter'; SELECT @dropIndexSql2 += N'ALTER TABLE' + QUOTENAME(SCHEMA_NAME(o.[schema_id])) + '.' + QUOTENAME(o.name) + ' DROP CONSTRAINT ' + QUOTENAME(c.name) + ';' FROM sys.key_constraints c INNER JOIN sys.tables AS o ON c.[parent_object_id] = o.[object_id] WHERE o.is_ms_shipped = 0 AND SCHEMA_NAME(o.[schema_id]) = '$(HangFireSchema)' AND o.name = 'Counter' EXEC sp_executesql @dropIndexSql2; PRINT 'Dropped all indexes on the [$(HangFireSchema)].[Counter] table'; -- [Counter].[Id] column can already be added to make replication work as written above, so we will re-create it -- to ensure it is in the expected format. PRINT 'Checking for existence of the [$(HangFireSchema)].[Counter].[Id] column'; IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Counter' AND COLUMN_NAME = 'Id' AND TABLE_SCHEMA='$(HangFireSchema)') BEGIN ALTER TABLE [$(HangFireSchema)].[Counter] DROP COLUMN [Id]; PRINT 'Dropped [$(HangFireSchema)].[Counter].[Id] column'; END ALTER TABLE [$(HangFireSchema)].[Counter] ADD [Id] BIGINT IDENTITY(1, 1); PRINT 'Created [$(HangFireSchema)].[Counter].[Id] column'; ALTER TABLE [$(HangFireSchema)].[Counter] ADD CONSTRAINT [PK_HangFire_Counter] PRIMARY KEY CLUSTERED ( [Key] ASC, [Id] ASC ); PRINT 'Created clustered primary key PK_HangFire_Counter ([Key], [Id])'; -- SqlServerStorageOptions.UseIgnoreDupKeyOption will yield much better results with INSERT/UPDATE operators -- instead of MERGE for [Set] and [Hash] tables. This change is also compatible with older clients, since -- MERGE operator is used there, so those clients are forward-compatible with these changes. ALTER TABLE [$(HangFireSchema)].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON); PRINT 'Enabled IGNORE_DUP_KEY option for [$(HangFireSchema)].[Set] table'; ALTER TABLE [$(HangFireSchema)].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON); PRINT 'Enabled IGNORE_DUP_KEY option for [$(HangFireSchema)].[Hash] table'; ALTER TABLE [$(HangFireSchema)].[JobQueue] DROP CONSTRAINT [PK_HangFire_JobQueue]; PRINT 'Dropped constraint [PK_HangFire_JobQueue] to modify the [$(HangFireSchema)].[JobQueue].[Id] column'; ALTER TABLE [$(HangFireSchema)].[JobQueue] ALTER COLUMN [Id] BIGINT NOT NULL; PRINT 'Changed [$(HangFireSchema)].[JobQueue].[Id] column type to BIGINT'; ALTER TABLE [$(HangFireSchema)].[JobQueue] ADD CONSTRAINT [PK_HangFire_JobQueue] PRIMARY KEY CLUSTERED ( [Queue] ASC, [Id] ASC ); PRINT 'Re-created constraint [PK_HangFire_JobQueue]'; SET @CURRENT_SCHEMA_VERSION = 8; END IF @CURRENT_SCHEMA_VERSION = 8 BEGIN PRINT 'Installing schema version 9'; CREATE NONCLUSTERED INDEX [IX_HangFire_State_CreatedAt] ON [$(HangFireSchema)].[State] ([CreatedAt] ASC) SET @CURRENT_SCHEMA_VERSION = 9; END /*IF @CURRENT_SCHEMA_VERSION = 9 BEGIN PRINT 'Installing schema version 10'; Insert migration here SET @CURRENT_SCHEMA_VERSION = 10; END*/ UPDATE [$(HangFireSchema)].[Schema] SET [Version] = @CURRENT_SCHEMA_VERSION IF @@ROWCOUNT = 0 INSERT INTO [$(HangFireSchema)].[Schema] ([Version]) VALUES (@CURRENT_SCHEMA_VERSION) PRINT 'Hangfire database schema installed'; COMMIT TRANSACTION; PRINT 'Hangfire SQL objects installed'; ================================================ FILE: src/Hangfire.SqlServer/PersistentJobQueueProviderCollection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections; using System.Collections.Generic; namespace Hangfire.SqlServer { public class PersistentJobQueueProviderCollection : IEnumerable { private readonly List _providers = new List(); private readonly Dictionary _providersByQueue = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly IPersistentJobQueueProvider _defaultProvider; public PersistentJobQueueProviderCollection(IPersistentJobQueueProvider defaultProvider) { if (defaultProvider == null) throw new ArgumentNullException(nameof(defaultProvider)); _defaultProvider = defaultProvider; _providers.Add(_defaultProvider); } public void Add(IPersistentJobQueueProvider provider, IEnumerable queues) { if (provider == null) throw new ArgumentNullException(nameof(provider)); if (queues == null) throw new ArgumentNullException(nameof(queues)); _providers.Add(provider); foreach (var queue in queues) { _providersByQueue.Add(queue, provider); } } public IPersistentJobQueueProvider GetProvider(string queue) { return _providersByQueue.TryGetValue(queue, out var provider) ? provider : _defaultProvider; } public IEnumerator GetEnumerator() { return _providers.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } } ================================================ FILE: src/Hangfire.SqlServer/Properties/AssemblyInfo.cs ================================================ using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Hangfire.SqlServer")] [assembly: AssemblyDescription("SQL Server job storage for Hangfire")] [assembly: Guid("3d96bf2f-8854-4872-aee3-faf81d121a4d")] [assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("Hangfire.SqlServer.Tests")] // Allow the generation of mocks for internal types [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] ================================================ FILE: src/Hangfire.SqlServer/SqlCommandBatch.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Data.Common; namespace Hangfire.SqlServer { internal sealed class SqlCommandBatch : IDisposable { private readonly List _commandList = new List(); private readonly SqlCommandSet _commandSet; private readonly int _defaultTimeout; public SqlCommandBatch(DbConnection connection, DbTransaction transaction, bool preferBatching) { Connection = connection; Transaction = transaction; if (preferBatching) { try { _commandSet = new SqlCommandSet(connection); _defaultTimeout = _commandSet.BatchCommand.CommandTimeout; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _commandSet = null; } } } public DbConnection Connection { get; } public DbTransaction Transaction { get; } public int? CommandTimeout { get; set; } public int? CommandBatchMaxTimeout { get; set; } public void Dispose() { foreach (var command in _commandList) { command.Dispose(); } _commandSet?.Dispose(); } public void Append(DbCommand command) { if (_commandSet != null) { _commandSet.Append(command); } else { _commandList.Add(command); } } public void ExecuteNonQuery() { if (_commandSet != null && _commandSet.CommandCount > 0) { _commandSet.Connection = Connection; _commandSet.Transaction = Transaction; var batchTimeout = CommandTimeout ?? _defaultTimeout; if (batchTimeout > 0) { batchTimeout = batchTimeout * _commandSet.CommandCount; if (CommandBatchMaxTimeout.HasValue) { batchTimeout = Math.Min(CommandBatchMaxTimeout.Value, batchTimeout); } } _commandSet.BatchCommand.CommandTimeout = batchTimeout; _commandSet.ExecuteNonQuery(); } foreach (var command in _commandList) { command.Connection = Connection; command.Transaction = Transaction; if (CommandTimeout.HasValue) { command.CommandTimeout = CommandTimeout.Value; } command.ExecuteNonQuery(); } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlCommandSet.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Data.Common; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Hangfire.SqlServer { internal sealed class SqlCommandSet : IDisposable { private static readonly ConcurrentDictionary SqlCommandSetType = new ConcurrentDictionary(); private static readonly ConcurrentDictionary> SetConnection = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> SetTransaction = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> GetBatchCommand = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary BatchCommandProperty = new ConcurrentDictionary(); private static readonly ConcurrentDictionary> AppendMethod = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> ExecuteNonQueryMethod = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> DisposeMethod = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> SqlCommandSetConstructor = new ConcurrentDictionary>(); private readonly object _instance; private readonly Action _setConnection; private readonly Action _setTransaction; private readonly Func _getBatchCommand; private readonly Action _appendMethod; private readonly Func _executeNonQueryMethod; private readonly Action _disposeMethod; private readonly Func _constructor; public SqlCommandSet(DbConnection connection) { Type sqlCommandSetType; try { sqlCommandSetType = SqlCommandSetType.GetOrAdd(connection.GetType().GetTypeInfo().Assembly, static sqlClientAssembly => { var assemblyName = sqlClientAssembly.GetName(); var version = assemblyName.Version; if (assemblyName.Name == "System.Data.SqlClient" && Version.Parse("4.0.0.0") < version && version < Version.Parse("4.6.0.0")) { // .NET Core version of the System.Data.SqlClient package below 4.7.0 (which // has assembly version 4.6.0.0) doesn't properly implement the SqlCommandSet // class, throwing the following exception in run-time: // ArgumentException: Specified parameter name 'Parameter1' is not valid. // GitHub Issue: https://github.com/dotnet/corefx/issues/29391 throw new NotSupportedException(".NET Core version of the System.Data.SqlClient package below 4.7.0 (which has assembly version 4.6.0.0) doesn't properly implement the SqlCommandSet class."); } var type = sqlClientAssembly.GetTypes().FirstOrDefault(static x => x.Name == "SqlCommandSet"); if (type == null) { throw new TypeLoadException($"Could not load type 'SqlCommandSet' from assembly '{sqlClientAssembly}'."); } return type; }); _setConnection = SetConnection.GetOrAdd(sqlCommandSetType, static type => { var p = Expression.Parameter(typeof(object)); var converted = Expression.Convert(p, type); var connectionParameter = Expression.Parameter(typeof(DbConnection)); var connectionProperty = type.GetProperty("Connection", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException($"Property '{type.FullName}.Connection' not found."); return Expression.Lambda>(Expression.Assign(Expression.Property(converted, connectionProperty), Expression.Convert(connectionParameter, connectionProperty.PropertyType)), p, connectionParameter).Compile(); }); _setTransaction = SetTransaction.GetOrAdd(sqlCommandSetType, static type => { var p = Expression.Parameter(typeof(object)); var converted = Expression.Convert(p, type); var transactionParameter = Expression.Parameter(typeof(DbTransaction)); var transactionProperty = type.GetProperty("Transaction", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException($"Property '{type.FullName}.Transaction' not found."); return Expression.Lambda>(Expression.Assign(Expression.Property(converted, transactionProperty), Expression.Convert(transactionParameter, transactionProperty.PropertyType)), p, transactionParameter).Compile(); }); var batchCommandProperty = BatchCommandProperty.GetOrAdd(sqlCommandSetType, static type => { return type.GetProperty("BatchCommand", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException($"Property '{type.FullName}.BatchCommand' not found."); }); _getBatchCommand = GetBatchCommand.GetOrAdd(sqlCommandSetType, type => { var p = Expression.Parameter(typeof(object)); var converted = Expression.Convert(p, type); return Expression.Lambda>(Expression.Property(converted, batchCommandProperty), p).Compile(); }); _appendMethod = AppendMethod.GetOrAdd(sqlCommandSetType, type => { var p = Expression.Parameter(typeof(object)); var converted = Expression.Convert(p, type); var batchCommandParameter = Expression.Parameter(typeof(DbCommand)); return Expression.Lambda>(Expression.Call(converted, "Append", null, Expression.Convert(batchCommandParameter, batchCommandProperty.PropertyType)), p, batchCommandParameter).Compile(); }); _executeNonQueryMethod = ExecuteNonQueryMethod.GetOrAdd(sqlCommandSetType, static type => { var p = Expression.Parameter(typeof(object)); var converted = Expression.Convert(p, type); return Expression.Lambda>(Expression.Call(converted, "ExecuteNonQuery", null), p).Compile(); }); _disposeMethod = DisposeMethod.GetOrAdd(sqlCommandSetType, static type => { var p = Expression.Parameter(typeof(object)); var converted = Expression.Convert(p, type); return Expression.Lambda>(Expression.Call(converted, "Dispose", null), p).Compile(); }); _constructor = SqlCommandSetConstructor.GetOrAdd(sqlCommandSetType, static type => { var ctor = Expression.New(type); return Expression.Lambda>(ctor).Compile(); }); } catch (Exception exception) when (exception.IsCatchableExceptionType()) { throw new NotSupportedException($"SqlCommandSet for {connection.GetType().FullName} is not supported, use regular commands instead", exception); } _instance = _constructor(); } public DbConnection Connection { set => _setConnection(_instance, value); } public DbTransaction Transaction { set => _setTransaction(_instance, value); } public DbCommand BatchCommand => _getBatchCommand(_instance); public int CommandCount { get; private set; } public void Append(DbCommand command) { _appendMethod(_instance, command); CommandCount++; } public int ExecuteNonQuery() { if (CommandCount == 0) { return 0; } return _executeNonQueryMethod(_instance); } public void Dispose() { _disposeMethod(_instance); } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerBootstrapperConfigurationExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.SqlServer { public static class SqlServerBootstrapperConfigurationExtensions { /// /// Tells the bootstrapper to use SQL Server as a job storage, /// that can be accessed using the given connection string or /// its name. /// /// Configuration /// Connection string or its name [Obsolete("Please use `GlobalConfiguration.UseSqlServerStorage` instead. Will be removed in version 2.0.0.")] public static SqlServerStorage UseSqlServerStorage( this IBootstrapperConfiguration configuration, string nameOrConnectionString) { var storage = new SqlServerStorage(nameOrConnectionString); configuration.UseStorage(storage); return storage; } /// /// Tells the bootstrapper to use SQL Server as a job storage /// with the given options, that can be accessed using the specified /// connection string or its name. /// /// Configuration /// Connection string or its name /// Advanced options [Obsolete("Please use `GlobalConfiguration.UseSqlServerStorage` instead. Will be removed in version 2.0.0.")] public static SqlServerStorage UseSqlServerStorage( this IBootstrapperConfiguration configuration, string nameOrConnectionString, SqlServerStorageOptions options) { var storage = new SqlServerStorage(nameOrConnectionString, options); configuration.UseStorage(storage); return storage; } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerConnection.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using System.Threading; using Dapper; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Server; using Hangfire.SqlServer.Entities; using Hangfire.Storage; // ReSharper disable RedundantAnonymousTypePropertyName namespace Hangfire.SqlServer { internal sealed class SqlServerConnection : JobStorageConnection { private readonly SqlServerStorage _storage; private readonly Dictionary> _lockedResources = new Dictionary>(); public SqlServerConnection([NotNull] SqlServerStorage storage) { if (storage == null) throw new ArgumentNullException(nameof(storage)); _storage = storage; } public SqlServerStorage Storage => _storage; public DbConnection DedicatedConnection => _dedicatedConnection; public override void Dispose() { if (_dedicatedConnection != null) { _dedicatedConnection.Dispose(); _dedicatedConnection = null; } base.Dispose(); } public override IWriteOnlyTransaction CreateWriteTransaction() { return new SqlServerWriteOnlyTransaction(this); } public override IDisposable AcquireDistributedLock([NotNull] string resource, TimeSpan timeout) { if (String.IsNullOrWhiteSpace(resource)) throw new ArgumentNullException(nameof(resource)); return AcquireLock($"{_storage.SchemaName}:{resource}", timeout); } public override IFetchedJob FetchNextJob(string[] queues, CancellationToken cancellationToken) { if (queues == null || queues.Length == 0) throw new ArgumentNullException(nameof(queues)); var providers = queues .Select(queue => _storage.QueueProviders.GetProvider(queue)) .Distinct() .ToArray(); if (providers.Length != 1) { throw new InvalidOperationException( $"Multiple provider instances registered for queues: {String.Join(", ", queues)}. You should choose only one type of persistent queues per server instance."); } var persistentQueue = providers[0].GetJobQueue(); return persistentQueue.Dequeue(queues, cancellationToken); } public override string CreateExpiredJob( Job job, IDictionary parameters, DateTime createdAt, TimeSpan expireIn) { if (job == null) throw new ArgumentNullException(nameof(job)); if (parameters == null) throw new ArgumentNullException(nameof(parameters)); var queryString = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].Job (InvocationData, Arguments, CreatedAt, ExpireAt) output inserted.Id values (@invocationData, @arguments, @createdAt, @expireAt)"); var invocationData = InvocationData.SerializeJob(job); var payload = invocationData.SerializePayload(excludeArguments: true); Action queryParameters = cmd => cmd .AddParameter("@invocationData", payload, DbType.String, size: -1) .AddParameter("@arguments", invocationData.Arguments, DbType.String, size: -1) .AddParameter("@createdAt", createdAt, DbType.DateTime) .AddParameter("@expireAt", createdAt.Add(expireIn), DbType.DateTime); Action additionalParameters = null; var parametersArray = parameters.ToArray(); if (parametersArray.Length <= 4) { if (parametersArray.Length == 1) { queryString = _storage.GetQueryFromTemplate(static schemaName => $@" set xact_abort on; set nocount on; declare @jobId bigint; begin tran; insert into [{schemaName}].Job (InvocationData, Arguments, CreatedAt, ExpireAt) values (@invocationData, @arguments, @createdAt, @expireAt); select @jobId = scope_identity(); select @jobId; insert into [{schemaName}].JobParameter (JobId, Name, Value) values (@jobId, @name, @value); commit tran;"); additionalParameters = cmd => cmd .AddParameter("@name", parametersArray[0].Key, DbType.String, size: 40) .AddParameter("@value", parametersArray[0].Value, DbType.String, size: -1); } else if (parametersArray.Length == 2) { queryString = _storage.GetQueryFromTemplate(static schemaName => $@" set xact_abort on; set nocount on; declare @jobId bigint; begin tran; insert into [{schemaName}].Job (InvocationData, Arguments, CreatedAt, ExpireAt) values (@invocationData, @arguments, @createdAt, @expireAt); select @jobId = scope_identity(); select @jobId; insert into [{schemaName}].JobParameter (JobId, Name, Value) values (@jobId, @name1, @value1), (@jobId, @name2, @value2); commit tran;"); additionalParameters = cmd => cmd .AddParameter("@name1", parametersArray[0].Key, DbType.String, size: 40) .AddParameter("@value1", parametersArray[0].Value, DbType.String, size: -1) .AddParameter("@name2", parametersArray[1].Key, DbType.String, size: 40) .AddParameter("@value2", parametersArray[1].Value, DbType.String, size: -1); } else if (parametersArray.Length == 3) { queryString = _storage.GetQueryFromTemplate(static schemaName => $@" set xact_abort on; set nocount on; declare @jobId bigint; begin tran; insert into [{schemaName}].Job (InvocationData, Arguments, CreatedAt, ExpireAt) values (@invocationData, @arguments, @createdAt, @expireAt); select @jobId = scope_identity(); select @jobId; insert into [{schemaName}].JobParameter (JobId, Name, Value) values (@jobId, @name1, @value1), (@jobId, @name2, @value2), (@jobId, @name3, @value3); commit tran;"); additionalParameters = cmd => cmd .AddParameter("@name1", parametersArray[0].Key, DbType.String, size: 40) .AddParameter("@value1", parametersArray[0].Value, DbType.String, size: -1) .AddParameter("@name2", parametersArray[1].Key, DbType.String, size: 40) .AddParameter("@value2", parametersArray[1].Value, DbType.String, size: -1) .AddParameter("@name3", parametersArray[2].Key, DbType.String, size: 40) .AddParameter("@value3", parametersArray[2].Value, DbType.String, size: -1); } else if (parametersArray.Length == 4) { queryString = _storage.GetQueryFromTemplate(static schemaName => $@" set xact_abort on; set nocount on; declare @jobId bigint; begin tran; insert into [{schemaName}].Job (InvocationData, Arguments, CreatedAt, ExpireAt) values (@invocationData, @arguments, @createdAt, @expireAt); select @jobId = scope_identity(); select @jobId; insert into [{schemaName}].JobParameter (JobId, Name, Value) values (@jobId, @name1, @value1), (@jobId, @name2, @value2), (@jobId, @name3, @value3), (@jobId, @name4, @value4); commit tran;"); additionalParameters = cmd => cmd .AddParameter("@name1", parametersArray[0].Key, DbType.String, size: 40) .AddParameter("@value1", parametersArray[0].Value, DbType.String, size: -1) .AddParameter("@name2", parametersArray[1].Key, DbType.String, size: 40) .AddParameter("@value2", parametersArray[1].Value, DbType.String, size: -1) .AddParameter("@name3", parametersArray[2].Key, DbType.String, size: 40) .AddParameter("@value3", parametersArray[2].Value, DbType.String, size: -1) .AddParameter("@name4", parametersArray[3].Key, DbType.String, size: 40) .AddParameter("@value4", parametersArray[3].Value, DbType.String, size: -1); } return _storage.UseConnection(_dedicatedConnection, static (storage, connection, ctx) => { using var command = connection.Create(ctx.Key, timeout: storage.CommandTimeout); ctx.Value.Key(command); ctx.Value.Value?.Invoke(command); return Convert.ToInt64(command.ExecuteScalar(), CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture); }, new KeyValuePair, Action>>( queryString, new KeyValuePair, Action>(queryParameters, additionalParameters))); } return _storage.UseTransaction(_dedicatedConnection, static (storage, connection, transaction, triple) => { using var jobCommand = connection.Create(triple.Item1, timeout: storage.CommandTimeout); triple.Item2(jobCommand); jobCommand.Transaction = transaction; var jobId = Convert.ToInt64(jobCommand.ExecuteScalar(), CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture); var query = storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].JobParameter (JobId, Name, Value) values (@jobId, @name, @value)"); using (var commandBatch = new SqlCommandBatch(connection, transaction, preferBatching: storage.CommandBatchMaxTimeout.HasValue)) { commandBatch.CommandTimeout = storage.CommandTimeout; commandBatch.CommandBatchMaxTimeout = storage.CommandBatchMaxTimeout; foreach (var parameter in triple.Item3) { var command = connection.Create(query) .AddParameter("@jobId", long.Parse(jobId, CultureInfo.InvariantCulture), DbType.Int64) .AddParameter("@name", parameter.Key, DbType.String, size: 40) .AddParameter("@value", (object)parameter.Value ?? DBNull.Value, DbType.String, size: -1); commandBatch.Append(command); } commandBatch.ExecuteNonQuery(); } return jobId; }, CreateTriple(queryString, queryParameters, parametersArray), null); } public override JobData GetJobData(string id) { if (id == null) throw new ArgumentNullException(nameof(id)); if (!long.TryParse(id, out var parsedId)) { return null; } return _storage.UseConnection(_dedicatedConnection, static (storage, connection, parsedId) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select InvocationData, StateName, Arguments, CreatedAt from [{schemaName}].Job with (readcommittedlock, forceseek) where Id = @id select Name, Value from [{schemaName}].JobParameter with (forceseek) where JobId = @id"); using (var multi = connection.QueryMultiple(query, new { id = parsedId }, commandTimeout: storage.CommandTimeout)) { var jobData = multi.ReadSingleOrDefault(); if (jobData == null) return null; var parameters = new Dictionary(); var jobParameters = multi.Read().ToArray(); for (var i = 0; i < jobParameters.Length; i++) { var jobParameter = jobParameters[i]; parameters[jobParameter.Name] = jobParameter.Value; } // TODO: conversion exception could be thrown. var invocationData = InvocationData.DeserializePayload(jobData.InvocationData); if (!String.IsNullOrEmpty(jobData.Arguments)) { invocationData.Arguments = jobData.Arguments; } Job job = null; JobLoadException loadException = null; try { job = invocationData.DeserializeJob(); } catch (JobLoadException ex) { loadException = ex; } return new JobData { Job = job, InvocationData = invocationData, State = jobData.StateName, CreatedAt = jobData.CreatedAt, LoadException = loadException, ParametersSnapshot = parameters }; } }, parsedId); } public override StateData GetStateData(string jobId) { if (jobId == null) throw new ArgumentNullException(nameof(jobId)); if (!long.TryParse(jobId, out var parsedId)) { return null; } return _storage.UseConnection(_dedicatedConnection, static (storage, connection, parsedId) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select s.Name, s.Reason, s.Data from [{schemaName}].State s with (readcommittedlock, forceseek) inner join [{schemaName}].Job j with (readcommittedlock, forceseek) on j.StateId = s.Id and j.Id = s.JobId where j.Id = @jobId"); var sqlState = connection.QuerySingleOrDefault(query, new { jobId = parsedId }, commandTimeout: storage.CommandTimeout); if (sqlState == null) { return null; } var data = new Dictionary( SerializationHelper.Deserialize>(sqlState.Data), StringComparer.OrdinalIgnoreCase); return new StateData { Name = sqlState.Name, Reason = sqlState.Reason, Data = data }; }, parsedId); } public override void SetJobParameter(string id, string name, string value) { if (id == null) throw new ArgumentNullException(nameof(id)); if (name == null) throw new ArgumentNullException(nameof(name)); // First updated is required for older schema versions (5 and below), where // [IX_HangFire_JobParameter_JobIdAndName] index wasn't declared as unique, // to make our query resistant to (no matter what schema we are using): // // https://github.com/HangfireIO/Hangfire/issues/1743 (deadlocks) // https://github.com/HangfireIO/Hangfire/issues/1741 (duplicate entries) // https://github.com/HangfireIO/Hangfire/issues/1693#issuecomment-697976133 (records aren't updated) _storage.UseConnection(_dedicatedConnection, static (storage, connection, triple) => { var query = storage.GetQueryFromTemplate(static schemaName => $@" set xact_abort off; begin try update [{schemaName}].JobParameter set Value = @value where JobId = @jobId and Name = @name; if @@ROWCOUNT = 0 insert into [{schemaName}].JobParameter (JobId, Name, Value) values (@jobId, @name, @value); end try begin catch declare @em nvarchar(4000), @es int, @est int; select @em=error_message(),@es=error_severity(),@est=error_state(); IF ERROR_NUMBER() not in (2601, 2627) raiserror(@em, @es, @est); update [{schemaName}].JobParameter set Value = @value where JobId = @jobId and Name = @name; end catch"); return connection.Execute( query, new { jobId = triple.Item1, name = triple.Item2, value = triple.Item3 }, commandTimeout: storage.CommandTimeout); }, CreateTriple(long.Parse(id, CultureInfo.InvariantCulture), name, value)); } public override string GetJobParameter(string id, string name) { if (id == null) throw new ArgumentNullException(nameof(id)); if (name == null) throw new ArgumentNullException(nameof(name)); if (!long.TryParse(id, out var parsedId)) { return null; } return _storage.UseConnection(_dedicatedConnection, static (storage, connection, pair) => connection.ExecuteScalar( storage.GetQueryFromTemplate(static schemaName => $@"select top (1) Value from [{schemaName}].JobParameter with (forceseek) where JobId = @id and Name = @name"), new { id = pair.Key, name = pair.Value }, commandTimeout: storage.CommandTimeout), new KeyValuePair(parsedId, name)); } public override HashSet GetAllItemsFromSet(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select Value from [{schemaName}].[Set] with (forceseek) where [Key] = @key"); var result = connection.Query( query, new { key = key }, commandTimeout: storage.CommandTimeout); return new HashSet(result); }, key); } public override string GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore) { return GetFirstByLowestScoreFromSet(key, fromScore, toScore, 1).FirstOrDefault(); } public override List GetFirstByLowestScoreFromSet(string key, double fromScore, double toScore, int count) { if (key == null) throw new ArgumentNullException(nameof(key)); if (count <= 0) throw new ArgumentException("The value must be a positive number", nameof(count)); if (toScore < fromScore) throw new ArgumentException("The `toScore` value must be higher or equal to the `fromScore` value.", nameof(toScore)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, pair) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select top (@count) Value from [{schemaName}].[Set] with (forceseek) where [Key] = @key and Score between @from and @to order by Score"); var result = connection.Query( query, new { count = pair.Value.Item3, key = pair.Key, from = pair.Value.Item1, to = pair.Value.Item2 }, commandTimeout: storage.CommandTimeout); return result.ToList(); }, new KeyValuePair>(key, CreateTriple(fromScore, toScore, count))); } public override void SetRangeInHash(string key, IEnumerable> keyValuePairs) { if (key == null) throw new ArgumentNullException(nameof(key)); if (keyValuePairs == null) throw new ArgumentNullException(nameof(keyValuePairs)); _storage.UseTransaction(_dedicatedConnection, static (storage, connection, transaction, pair) => { var query = storage.GetQueryFromTemplate(static schemaName => $@" set xact_abort off; begin try insert into [{schemaName}].Hash ([Key], Field, Value) values (@key, @field, @value); if @@ROWCOUNT = 0 update [{schemaName}].Hash set Value = @value where [Key] = @key and Field = @field; end try begin catch declare @em nvarchar(4000), @es int, @est int; select @em=error_message(),@es=error_severity(),@est=error_state(); IF ERROR_NUMBER() not in (2601, 2627) raiserror(@em, @es, @est); update [{schemaName}].Hash set Value = @value where [Key] = @key and Field = @field; end catch"); var lockResourceKey = $"{storage.SchemaName}:Hash:Lock"; using (var commandBatch = new SqlCommandBatch(connection, transaction, preferBatching: storage.CommandBatchMaxTimeout.HasValue)) { if (!storage.Options.DisableGlobalLocks) { var command = connection .Create("SET XACT_ABORT ON;exec sp_getapplock @Resource=@resource, @LockMode=N'Exclusive', @LockOwner=N'Transaction', @LockTimeout=-1;") .AddParameter("@resource", lockResourceKey, DbType.String, size: 255); commandBatch.Append(command); } foreach (var keyValuePair in pair.Value) { var command = connection.Create(query) .AddParameter("@key", pair.Key, DbType.String) .AddParameter("@field", keyValuePair.Key, DbType.String, size: 100) .AddParameter("@value", (object)keyValuePair.Value ?? DBNull.Value, DbType.String, size: -1); commandBatch.Append(command); } if (!storage.Options.DisableGlobalLocks) { var command = connection .Create("exec sp_releaseapplock @Resource=@resource, @LockOwner=N'Transaction';") .AddParameter("@resource", lockResourceKey, DbType.String, size: 255); commandBatch.Append(command); } commandBatch.CommandTimeout = storage.CommandTimeout; commandBatch.CommandBatchMaxTimeout = storage.CommandBatchMaxTimeout; commandBatch.ExecuteNonQuery(); } }, new KeyValuePair>>(key, keyValuePairs)); } public override Dictionary GetAllEntriesFromHash(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select Field, Value from [{schemaName}].Hash with (forceseek) where [Key] = @key"); var result = connection.Query( query, new { key = key }, commandTimeout: storage.CommandTimeout) .ToDictionary(static x => x.Field, static x => x.Value); return result.Count != 0 ? result : null; }, key); } public override void AnnounceServer(string serverId, ServerContext context) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); if (context == null) throw new ArgumentNullException(nameof(context)); var data = new ServerData { WorkerCount = context.WorkerCount, Queues = context.Queues, StartedAt = DateTime.UtcNow, }; _storage.UseConnection(_dedicatedConnection, static (storage, connection, pair) => { var query = storage.GetQueryFromTemplate(static schemaName => $@";merge [{schemaName}].Server with (holdlock) as Target using (VALUES (@id, @data, sysutcdatetime())) as Source (Id, Data, Heartbeat) on Target.Id = Source.Id when matched then update set Data = Source.Data, LastHeartbeat = Source.Heartbeat when not matched then insert (Id, Data, LastHeartbeat) values (Source.Id, Source.Data, Source.Heartbeat);"); return connection.Execute( query, new { id = pair.Key, data = pair.Value }, commandTimeout: storage.CommandTimeout); }, new KeyValuePair(serverId, SerializationHelper.Serialize(data))); } public override void RemoveServer(string serverId) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); _storage.UseConnection(_dedicatedConnection, static (storage, connection, serverId) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"delete S from [{schemaName}].Server S with (forceseek) where Id = @id"); return connection.Execute( query, new { id = serverId }, commandTimeout: storage.CommandTimeout); }, serverId); } public override void Heartbeat(string serverId) { if (serverId == null) throw new ArgumentNullException(nameof(serverId)); _storage.UseConnection(_dedicatedConnection, static (storage, connection, serverId) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"update [{schemaName}].Server set LastHeartbeat = sysutcdatetime() where Id = @id"); var affected = connection.Execute( query, new { id = serverId }, commandTimeout: storage.CommandTimeout); if (affected == 0) { throw new BackgroundServerGoneException(); } return affected; }, serverId); } public override int RemoveTimedOutServers(TimeSpan timeOut) { if (timeOut.Duration() != timeOut) { throw new ArgumentException("The `timeOut` value must be positive.", nameof(timeOut)); } return _storage.UseConnection(_dedicatedConnection, static (storage, connection, timeout) => connection.Execute( storage.GetQueryFromTemplate(static schemaName => $@"delete s from [{schemaName}].Server s with (readpast, readcommitted) where LastHeartbeat < dateadd(ms, @timeoutMsNeg, sysutcdatetime())"), new { timeoutMsNeg = timeout.Negate().TotalMilliseconds }, commandTimeout: storage.CommandTimeout), timeOut); } public override long GetSetCount(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => connection.ExecuteScalar( storage.GetQueryFromTemplate(static schemaName => $@"select count(*) from [{schemaName}].[Set] with (forceseek) where [Key] = @key"), new { key = key }, commandTimeout: storage.CommandTimeout), key); } public override long GetSetCount(IEnumerable keys, int limit) { if (keys == null) throw new ArgumentNullException(nameof(keys)); if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit), "Value must be greater or equal to 0."); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, pair) => connection.ExecuteScalar( storage.GetQueryFromTemplate(static schemaName => $@"select count(*) from ( select top(@limit) 1 as N from [{schemaName}].[Set] with (forceseek) where [Key] in @keys ) a"), new { keys = pair.Key, limit = pair.Value }, commandTimeout: storage.CommandTimeout), new KeyValuePair, int>(keys, limit)); } public override bool GetSetContains(string key, string value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, pair) => connection.ExecuteScalar( storage.GetQueryFromTemplate(static schemaName => $@"select count(1) from [{schemaName}].[Set] with (forceseek) where [Key] = @key and [Value] = @value"), new { key = pair.Key, value = pair.Value }, commandTimeout: storage.CommandTimeout) == 1, new KeyValuePair(key, value)); } public override List GetRangeFromSet(string key, int startingFrom, int endingAt) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, triple) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select [Value] from ( select [Value], row_number() over (order by [Score] ASC) as row_num from [{schemaName}].[Set] with (forceseek) where [Key] = @key ) as s where s.row_num between @startingFrom and @endingAt"); return connection .Query(query, new { key = triple.Item1, startingFrom = triple.Item2 + 1, endingAt = triple.Item3 + 1 }, commandTimeout: storage.CommandTimeout) .ToList(); }, CreateTriple(key, startingFrom, endingAt)); } public override TimeSpan GetSetTtl(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select min([ExpireAt]) from [{schemaName}].[Set] with (forceseek) where [Key] = @key"); var result = connection.ExecuteScalar(query, new { key = key }, commandTimeout: storage.CommandTimeout); if (!result.HasValue) return TimeSpan.FromSeconds(-1); return result.Value - DateTime.UtcNow; }, key); } public override long GetCounter(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select sum(s.[Value]) from (select sum([Value]) as [Value] from [{schemaName}].Counter with (forceseek) where [Key] = @key union all select [Value] from [{schemaName}].AggregatedCounter with (forceseek) where [Key] = @key) as s"); return connection.ExecuteScalar(query, new { key = key }, commandTimeout: storage.CommandTimeout) ?? 0; }, key); } public override long GetHashCount(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select count(*) from [{schemaName}].Hash with (forceseek) where [Key] = @key"); return connection.ExecuteScalar(query, new { key = key }, commandTimeout: storage.CommandTimeout); }, key); } public override TimeSpan GetHashTtl(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select min([ExpireAt]) from [{schemaName}].Hash with (forceseek) where [Key] = @key"); var result = connection.ExecuteScalar(query, new { key = key }, commandTimeout: storage.CommandTimeout); if (!result.HasValue) return TimeSpan.FromSeconds(-1); return result.Value - DateTime.UtcNow; }, key); } public override string GetValueFromHash(string key, string name) { if (key == null) throw new ArgumentNullException(nameof(key)); if (name == null) throw new ArgumentNullException(nameof(name)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, pair) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select [Value] from [{schemaName}].Hash with (forceseek) where [Key] = @key and [Field] = @field"); return connection.ExecuteScalar(query, new { key = pair.Key, field = pair.Value }, commandTimeout: storage.CommandTimeout); }, new KeyValuePair(key, name)); } public override long GetListCount(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select count(*) from [{schemaName}].List with (forceseek) where [Key] = @key"); return connection.ExecuteScalar(query, new { key = key }, commandTimeout: storage.CommandTimeout); }, key); } public override TimeSpan GetListTtl(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select min([ExpireAt]) from [{schemaName}].List with (forceseek) where [Key] = @key"); var result = connection.ExecuteScalar(query, new { key = key }, commandTimeout: storage.CommandTimeout); if (!result.HasValue) return TimeSpan.FromSeconds(-1); return result.Value - DateTime.UtcNow; }, key); } public override List GetRangeFromList(string key, int startingFrom, int endingAt) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, triple) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select [Value] from ( select [Value], row_number() over (order by [Id] desc) as row_num from [{schemaName}].List with (forceseek) where [Key] = @key ) as s where s.row_num between @startingFrom and @endingAt"); return connection .Query(query, new { key = triple.Item1, startingFrom = triple.Item2 + 1, endingAt = triple.Item3 + 1 }, commandTimeout: storage.CommandTimeout) .ToList(); }, CreateTriple(key, startingFrom, endingAt)); } public override List GetAllItemsFromList(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return _storage.UseConnection(_dedicatedConnection, static (storage, connection, key) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select [Value] from [{schemaName}].List with (forceseek) where [Key] = @key order by [Id] desc"); return connection .Query(query, new { key = key }, commandTimeout: storage.CommandTimeout) .ToList(); }, key); } public override DateTime GetUtcDateTime() { return _storage.UseConnection(_dedicatedConnection, static (_, connection) => DateTime.SpecifyKind(connection.ExecuteScalar("SELECT SYSUTCDATETIME()"), DateTimeKind.Utc)); } private DbConnection _dedicatedConnection; internal DisposableLock AcquireLock(string resource, TimeSpan timeout) { if (_dedicatedConnection == null) { _dedicatedConnection = _storage.CreateAndOpenConnection(); } var lockId = Guid.NewGuid(); var ownLock = false; if (!_lockedResources.TryGetValue(resource, out var lockIds)) { try { SqlServerDistributedLock.Acquire(_dedicatedConnection, resource, timeout); ownLock = true; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ReleaseLock(resource, lockId, true, false); throw; } _lockedResources.Add(resource, lockIds = new HashSet()); } lockIds.Add(lockId); return new DisposableLock(this, resource, lockId, ownLock); } private void ReleaseLock(string resource, Guid lockId, bool onDisposing, bool releasedExternally) { try { if (_lockedResources.TryGetValue(resource, out var lockIds)) { if (lockIds.Contains(lockId)) { if (lockIds.Remove(lockId) && lockIds.Count == 0 && _lockedResources.Remove(resource) && _dedicatedConnection.State == ConnectionState.Open) { // Session-scoped application locks are held only when connection // is open. When connection is closed or broken, for example, when // there was an error, application lock is already released by SQL // Server itself, and we shouldn't do anything. if (!releasedExternally) { SqlServerDistributedLock.Release(_dedicatedConnection, resource); } } } } } catch (Exception ex) when (ex.IsCatchableExceptionType()) { if (!onDisposing) { throw; } } finally { if (_lockedResources.Count == 0) { _storage.ReleaseConnection(_dedicatedConnection); _dedicatedConnection = null; } } } internal sealed class DisposableLock : IDisposable { private bool _disposed; private readonly SqlServerConnection _connection; private readonly string _resource; private readonly Guid _lockId; public DisposableLock(SqlServerConnection connection, string resource, Guid lockId, bool ownLock) { _connection = connection; _resource = resource; _lockId = lockId; OwnLock = ownLock; } public string Resource => _resource; public bool OwnLock { get; } public bool ReleasedExternally { get; private set; } public void Dispose() { if (_disposed) return; _disposed = true; _connection.ReleaseLock(_resource, _lockId, true, ReleasedExternally); } public void TryReportReleased() { if (OwnLock) ReleasedExternally = true; } } private static ValueTriple CreateTriple(T1 item1, T2 item2, T3 item3) { return new ValueTriple(item1, item2, item3); } // .NET Framework 4.5 doesn't have ValueTuple class. private readonly struct ValueTriple(T1 item1, T2 item2, T3 item3) { public T1 Item1 { get; } = item1; public T2 Item2 { get; } = item2; public T3 Item3 { get; } = item3; } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerDistributedLock.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using Dapper; using Hangfire.Annotations; using Hangfire.Storage; namespace Hangfire.SqlServer { public class SqlServerDistributedLock : IDisposable { private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(1); private const string LockMode = "Exclusive"; private const string LockOwner = "Session"; // Connections to SQL Azure Database that are idle for 30 minutes // or longer will be terminated. And since we are using separate // connection for a distributed lock, we'd like to prevent Resource // Governor from terminating it. private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1); private static readonly IDictionary LockErrorMessages = new Dictionary { { -1, "The lock request timed out" }, { -2, "The lock request was canceled" }, { -3, "The lock request was chosen as a deadlock victim" }, { -999, "Indicates a parameter validation or other call error" } }; private static readonly ThreadLocal> AcquiredLocks = new ThreadLocal>(static () => new Dictionary()); private DbConnection _connection; private readonly SqlServerStorage _storage; private readonly string _resource; private readonly Timer _timer; private readonly object _lockObject = new object(); private bool _completed; [Obsolete("Don't use this class directly, use SqlServerConnection.AcquireDistributedLock instead as it provides better safety. Will be removed in 2.0.0.")] [SuppressMessage("Performance", "CA1854:Prefer the \'IDictionary.TryGetValue(TKey, out TValue)\' method")] public SqlServerDistributedLock([NotNull] SqlServerStorage storage, [NotNull] string resource, TimeSpan timeout) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (String.IsNullOrEmpty(resource)) throw new ArgumentNullException(nameof(resource)); _storage = storage; _resource = resource; if (!AcquiredLocks.Value.ContainsKey(_resource) || AcquiredLocks.Value[_resource] == 0) { _connection = storage.CreateAndOpenConnection(); try { Acquire(_connection, _resource, timeout); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { storage.ReleaseConnection(_connection); throw; } if (!_storage.IsExistingConnection(_connection)) { _timer = new Timer(ExecuteKeepAliveQuery, null, KeepAliveInterval, KeepAliveInterval); } AcquiredLocks.Value[_resource] = 1; } else { AcquiredLocks.Value[_resource]++; } } [SuppressMessage("Performance", "CA1854:Prefer the \'IDictionary.TryGetValue(TKey, out TValue)\' method")] public void Dispose() { if (_completed) return; _completed = true; if (!AcquiredLocks.Value.ContainsKey(_resource)) return; AcquiredLocks.Value[_resource]--; if (AcquiredLocks.Value[_resource] != 0) return; lock (_lockObject) { // Timer callback may be invoked after the Dispose method call, // so we are using lock to avoid unsynchronized calls. try { AcquiredLocks.Value.Remove(_resource); _timer?.Dispose(); if (_connection.State == ConnectionState.Open) { // Session-scoped application locks are held only when connection // is open. When connection is closed or broken, for example, when // there was an error, application lock is already released by SQL // Server itself, and we shouldn't do anything. Release(_connection, _resource); } } finally { _storage.ReleaseConnection(_connection); _connection = null; } } GC.SuppressFinalize(this); } private void ExecuteKeepAliveQuery(object obj) { lock (_lockObject) { try { _connection?.Execute("SELECT 1;"); } catch { // Connection is broken. This means that distributed lock // was released, and we can't guarantee the safety property // for the code that is wrapped with this block. So it was // a bad idea to have a separate connection for just // distributed lock. // OBSOLETE. This class is not used anymore by the SqlServerConnection // class. The problem above was solved there by establishing a // dedicated connection, when there is at least one acquired lock. // Since the acquisition, all the commands and transactions are routed // through that connection to ensure all the locks are still active. } } } internal static void Acquire(DbConnection connection, string resource, TimeSpan timeout) { if (connection.State != ConnectionState.Open) { // When we are passing a closed connection to Dapper's Execute method, // it kindly opens it for us, but after command execution, it will be closed // automatically, and our just-acquired application lock will immediately // be released. This is not behavior we want to achieve, so let's throw an // exception instead. throw new InvalidOperationException("Connection must be open before acquiring a distributed lock."); } var started = Stopwatch.StartNew(); // We can't pass our timeout directly to the sp_getapplock stored procedure, because // high values, such as minute or more, may cause SQL Server's thread pool starvation, // when the number of connections that try to acquire a lock is more than the number of // available threads in SQL Server. In this case a deadlock will occur, when SQL Server // tries to schedule some more work for a connection that acquired a lock, but all the // available threads in a pool waiting for that lock to be released. // // So we are trying to acquire a lock multiple times instead, with timeout that's equal // to seconds, not minutes. var lockTimeout = (long) Math.Min(LockTimeout.TotalMilliseconds, timeout.TotalMilliseconds); do { using var command = connection .Create("sp_getapplock", CommandType.StoredProcedure, timeout: (int)(lockTimeout / 1000) + 5) .AddParameter("@Resource", resource, DbType.String, size: 255) .AddParameter("@DbPrincipal", "public", DbType.String, size: 32) .AddParameter("@LockMode", LockMode, DbType.String, size: 32) .AddParameter("@LockOwner", LockOwner, DbType.String, size: 32) .AddParameter("@LockTimeout", lockTimeout, DbType.Int32) .AddReturnParameter("@Result", out var resultParameter, DbType.Int32); command.ExecuteNonQuery(); var lockResult = (int)resultParameter.Value; if (lockResult >= 0) { // The lock has been successfully obtained on the specified resource. return; } if (lockResult == -999 /* Indicates a parameter validation or other call error. */) { throw new SqlServerDistributedLockException( $"Could not place a lock on the resource '{resource}': {(LockErrorMessages.TryGetValue(lockResult, out var message) ? message : $"Server returned the '{lockResult}' error.")}."); } } while (started.Elapsed < timeout); throw new DistributedLockTimeoutException(resource); } internal static void Release(DbConnection connection, string resource) { using (var command = CreateReleaseCommand(connection, resource, out var resultParameter)) { command.ExecuteNonQuery(); var releaseResult = (int)resultParameter.Value; if (releaseResult < 0) { throw new SqlServerDistributedLockException( $"Could not release a lock on the resource '{resource}': Server returned the '{releaseResult}' error."); } } } internal static DbCommand CreateReleaseCommand( DbConnection connection, string resource, out DbParameter resultParameter) { return connection.Create("sp_releaseapplock", CommandType.StoredProcedure) .AddParameter("@Resource", resource, DbType.String, size: 255) .AddParameter("@LockOwner", LockOwner, DbType.String, size: 32) .AddReturnParameter("@Result", out resultParameter, DbType.Int32); } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerDistributedLockException.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Runtime.Serialization; namespace Hangfire.SqlServer { #if !NETSTANDARD1_3 [Serializable] #endif public class SqlServerDistributedLockException : Exception { public SqlServerDistributedLockException(string message) : base(message) { } #if !NETSTANDARD1_3 /// /// Initializes a new instance of the class /// with serialized data. /// /// The that holds the serialized object data about the exception being thrown. /// The that contains contextual information about the source or destination. protected SqlServerDistributedLockException(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerHeartbeatProcess.cs ================================================ // This file is part of Hangfire. Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Threading; using Hangfire.Common; using Hangfire.Server; namespace Hangfire.SqlServer { #pragma warning disable CS0618 internal sealed class SqlServerHeartbeatProcess : IServerComponent, IBackgroundProcess #pragma warning restore CS0618 { private readonly ConcurrentDictionary _items = new ConcurrentDictionary(); public void Track(SqlServerTimeoutJob item) { _items.TryAdd(item, null); } public void Untrack(SqlServerTimeoutJob item) { _items.TryRemove(item, out _); } public void Execute(BackgroundProcessContext context) { Execute(context.ShutdownToken); } public void Execute(CancellationToken cancellationToken) { foreach (var item in _items) { item.Key.ExecuteKeepAliveQueryIfRequired(); } cancellationToken.Wait(TimeSpan.FromSeconds(1)); } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerJobQueue.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using System.Threading; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; // ReSharper disable RedundantAnonymousTypePropertyName namespace Hangfire.SqlServer { internal sealed class SqlServerJobQueue : IPersistentJobQueue { // This is an optimization that helps to overcome the polling delay, when // both client and server reside in the same process. Everything is working // without these events, but it helps to reduce the delays in processing. internal static readonly ConcurrentDictionary, AutoResetEvent> NewItemInQueueEvents = new(); private static readonly Func, SemaphoreSlim> CreateSemaphoreFunc = CreateSemaphore; private static readonly TimeSpan LongPollingThreshold = TimeSpan.FromSeconds(1); private static readonly int PollingQuantumMs = 1000; private static readonly int DefaultPollingDelayMs = 200; private static readonly int MinPollingDelayMs = 100; private static readonly ConcurrentDictionary, SemaphoreSlim> Semaphores = new ConcurrentDictionary, SemaphoreSlim>(); private readonly SqlServerStorage _storage; private readonly SqlServerStorageOptions _options; public SqlServerJobQueue([NotNull] SqlServerStorage storage, SqlServerStorageOptions options) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (options == null) throw new ArgumentNullException(nameof(options)); _storage = storage; _options = options; } [NotNull] public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken) { if (queues == null) throw new ArgumentNullException(nameof(queues)); if (queues.Length == 0) throw new ArgumentException("Queue array must be non-empty.", nameof(queues)); if (_options.SlidingInvisibilityTimeout.HasValue) { return DequeueUsingSlidingInvisibilityTimeout(queues, cancellationToken); } return DequeueUsingTransaction(queues, cancellationToken); } #if FEATURE_TRANSACTIONSCOPE public void Enqueue(IDbConnection connection, string queue, string jobId) #else public void Enqueue(DbConnection connection, DbTransaction transaction, string queue, string jobId) #endif { var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].JobQueue (JobId, Queue) values (@jobId, @queue)"); using var command = ((DbConnection)connection).Create(query, timeout: _storage.CommandTimeout); command.AddParameter("@jobId", long.Parse(jobId, CultureInfo.InvariantCulture), DbType.Int64); command.AddParameter("@queue", queue, DbType.String); #if !FEATURE_TRANSACTIONSCOPE command.Transaction = transaction; #endif command.ExecuteNonQuery(); } private SqlServerTimeoutJob DequeueUsingSlidingInvisibilityTimeout(string[] queues, CancellationToken cancellationToken) { if (queues == null) throw new ArgumentNullException(nameof(queues)); if (queues.Length == 0) throw new ArgumentException("Queue array must be non-empty.", nameof(queues)); cancellationToken.ThrowIfCancellationRequested(); // First we will check if our queues has any background jobs in it and // return if any. In this case we don't need any additional logic like // semaphores or waiting. var fetchedJob = FetchJob(queues); if (fetchedJob != null) return fetchedJob; // Then we determine whether we should use the long polling feature, // where only a single worker acquires a semaphore for each queue set // to avoid excessive load on a database. var configuredPollInterval = _options.QueuePollInterval; var useLongPolling = configuredPollInterval < LongPollingThreshold; // Then we determine a delay between attempts. For long-polling we use constrained // sub-second intervals within the [MinPollingDelayMs, PollingQuantumMs] interval. // For regular polling we just use the interval defined in the QueuePollInterval // option. var pollingDelayMs = useLongPolling ? TimeSpan.FromMilliseconds( Math.Min( Math.Max( configuredPollInterval == TimeSpan.Zero ? DefaultPollingDelayMs : (int)configuredPollInterval.TotalMilliseconds, MinPollingDelayMs), PollingQuantumMs)) : configuredPollInterval; var queuesString = String.Join("_", queues.OrderBy(static x => x)); var resource = Tuple.Create(_storage, queuesString); using var cancellationEvent = cancellationToken.GetCancellationEvent(); var waitArray = GetWaitArrayForQueueSignals(_storage, queues, cancellationEvent); SemaphoreSlim semaphore = null; try { semaphore = Semaphores.GetOrAdd(resource, CreateSemaphoreFunc); semaphore.Wait(cancellationToken); while (!cancellationToken.IsCancellationRequested) { // For non-first attempts we just trying again and again with // the determined delay between attempts, until shutdown // request is received. fetchedJob = FetchJob(queues); if (fetchedJob != null) return fetchedJob; WaitHandle.WaitAny(waitArray, pollingDelayMs); cancellationToken.ThrowIfCancellationRequested(); } } finally { if (semaphore != null && semaphore.CurrentCount == 0) { semaphore.Release(); } } cancellationToken.ThrowIfCancellationRequested(); return null; } private static SemaphoreSlim CreateSemaphore(Tuple _) { return new SemaphoreSlim(initialCount: 1); } private SqlServerTimeoutJob FetchJob(string[] queues) { return _storage.UseConnection(null, static (storage, connection, queues) => { if (!storage.Options.SlidingInvisibilityTimeout.HasValue) { throw new InvalidOperationException("This method should be called only when SlidingInvisibilityTimeout is set."); } var invisibilityTimeout = (int)storage.Options.SlidingInvisibilityTimeout.Value.Negate().TotalSeconds; using var command = CreateNonBlockingFetchCommand(storage, connection, queues, invisibilityTimeout); using var reader = command.ExecuteReader(); if (!reader.Read()) return null; var id = Convert.ToInt64(reader.GetValue(reader.GetOrdinal("Id")), CultureInfo.InvariantCulture); // Can be Int32 in older schemas var jobId = Convert.ToInt64(reader.GetValue(reader.GetOrdinal("JobId")), CultureInfo.InvariantCulture); // Can be Int32 in older schemas var queue = reader.GetString(reader.GetOrdinal("Queue")); var fetchedAt = reader.GetDateTime(reader.GetOrdinal("FetchedAt")); if (reader.Read()) { throw new InvalidOperationException("Multiple rows returned from SQL Server, while expecting single or none."); } return new SqlServerTimeoutJob(storage, id, jobId.ToString(CultureInfo.InvariantCulture), queue, fetchedAt); }, queues); } private static DbCommand CreateNonBlockingFetchCommand( SqlServerStorage storage, DbConnection connection, string[] queues, int invisibilityTimeout) { var template = storage.GetQueryFromTemplate(static schemaName => $@" set nocount on;set xact_abort on;set tran isolation level read committed; update top (1) JQ set FetchedAt = GETUTCDATE() output INSERTED.Id, INSERTED.JobId, INSERTED.Queue, INSERTED.FetchedAt from [{schemaName}].JobQueue JQ with (forceseek, readpast, updlock, rowlock) where Queue in @queues and (FetchedAt is null or FetchedAt < DATEADD(second, @timeoutSs, GETUTCDATE()));"); return connection.Create(template, timeout: storage.CommandTimeout) .AddParameter("@timeoutSs", invisibilityTimeout, DbType.Int32) .AddExpandedParameter("@queues", queues, DbType.String); } private SqlServerTransactionJob DequeueUsingTransaction(string[] queues, CancellationToken cancellationToken) { DbTransaction transaction = null; var pollInterval = _options.QueuePollInterval > TimeSpan.Zero ? _options.QueuePollInterval : TimeSpan.FromSeconds(1); using var cancellationEvent = cancellationToken.GetCancellationEvent(); var waitArray = GetWaitArrayForQueueSignals(_storage, queues, cancellationEvent); while (!cancellationToken.IsCancellationRequested) { var connection = _storage.CreateAndOpenConnection(); try { transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); #pragma warning disable 618 using var command = CreateTransactionalFetchCommand(_storage, connection, queues, (int)_options.InvisibilityTimeout.Negate().TotalSeconds); #pragma warning restore 618 command.Transaction = transaction; using (var reader = command.ExecuteReader()) { if (reader.Read()) { var jobId = Convert.ToInt64(reader.GetValue(reader.GetOrdinal("JobId")), CultureInfo.InvariantCulture); // Can be Int32 in older schemas var queue = reader.GetString(reader.GetOrdinal("Queue")); if (reader.Read()) { throw new InvalidOperationException( "Multiple rows returned from SQL Server, while expecting single or none."); } var result = new SqlServerTransactionJob(_storage, connection, transaction, jobId.ToString(CultureInfo.InvariantCulture), queue); // We shouldn't dispose them, because their ownership is now related // to the SqlServerTransactionJob instance. connection = null; transaction = null; return result; } } // Nothing updated, just commit the empty transaction. transaction.Commit(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // Check connection isn't broken first, and that transaction // can be rolled back without throwing InvalidOperationException // on older System.Data.SqlClient in .NET Core. // https://github.com/HangfireIO/Hangfire/issues/1494 // https://github.com/dotnet/efcore/issues/12864 if (transaction?.Connection != null) transaction.Rollback(); throw; } finally { transaction?.Dispose(); transaction = null; _storage.ReleaseConnection(connection); } WaitHandle.WaitAny(waitArray, pollInterval); } cancellationToken.ThrowIfCancellationRequested(); return null; } private static DbCommand CreateTransactionalFetchCommand( SqlServerStorage storage, DbConnection connection, string[] queues, int invisibilityTimeout) { var template = storage.GetQueryFromTemplate(static schemaName => $@"delete top (1) JQ output DELETED.Id, DELETED.JobId, DELETED.Queue from [{schemaName}].JobQueue JQ with (readpast, updlock, rowlock, forceseek) where Queue in @queues and (FetchedAt is null or FetchedAt < DATEADD(second, @timeout, GETUTCDATE()))"); return connection .Create(template, timeout: storage.CommandTimeout) .AddParameter("@timeout", invisibilityTimeout, DbType.Int32) .AddExpandedParameter("@queues", queues, DbType.String); } private static WaitHandle[] GetWaitArrayForQueueSignals(SqlServerStorage storage, string[] queues, CancellationTokenExtentions.CancellationEvent cancellationEvent) { var waitList = new List(capacity: queues.Length + 1) { cancellationEvent.WaitHandle }; foreach (var queue in queues) { waitList.Add(NewItemInQueueEvents.GetOrAdd(Tuple.Create(storage, queue), static _ => new AutoResetEvent(initialState: false))); } return waitList.ToArray(); } [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] private sealed class FetchedJob { public long Id { get; set; } public long JobId { get; set; } public string Queue { get; set; } public DateTime? FetchedAt { get; set; } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerJobQueueMonitoringApi.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Dapper; using Hangfire.Annotations; // ReSharper disable RedundantAnonymousTypePropertyName namespace Hangfire.SqlServer { internal sealed class SqlServerJobQueueMonitoringApi : IPersistentJobQueueMonitoringApi { private static readonly TimeSpan QueuesCacheTimeout = TimeSpan.FromSeconds(5); private readonly SqlServerStorage _storage; private readonly object _cacheLock = new object(); private List _queuesCache = new List(); private Stopwatch _cacheUpdated; public SqlServerJobQueueMonitoringApi([NotNull] SqlServerStorage storage) { if (storage == null) throw new ArgumentNullException(nameof(storage)); _storage = storage; } public IEnumerable GetQueues() { lock (_cacheLock) { if (_queuesCache.Count == 0 || _cacheUpdated.Elapsed > QueuesCacheTimeout) { var result = _storage.UseConnection(null, static (storage, connection) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select distinct(Queue) from [{schemaName}].JobQueue with (nolock)"); return connection.Query(query, commandTimeout: storage.CommandTimeout).Select(static x => (string) x.Queue).ToList(); }); _queuesCache = result; _cacheUpdated = Stopwatch.StartNew(); } return _queuesCache.ToList(); } } public IEnumerable GetEnqueuedJobIds(string queue, int from, int perPage) { return _storage.UseConnection(null, static (storage, connection, ctx) => { var query = storage.GetQueryFromTemplate(static schemaName => $@"select r.JobId from ( select jq.JobId, row_number() over (order by jq.Id) as row_num from [{schemaName}].JobQueue jq with (nolock, forceseek) where jq.Queue = @queue and jq.FetchedAt is null ) as r where r.row_num between @start and @end"); return connection.Query( query, new { queue = ctx.Queue, start = ctx.From + 1, end = ctx.From + ctx.PerPage }, commandTimeout: storage.CommandTimeout) .ToList() .Select(static x => x.JobId) .ToList(); }, new QueuePageQueryContext(queue, from, perPage)); } public IEnumerable GetFetchedJobIds(string queue, int from, int perPage) { return _storage.UseConnection(null, static (storage, connection, ctx) => { var query = storage.GetQueryFromTemplate(static schemaName => $@" select r.JobId from ( select jq.JobId, jq.FetchedAt, row_number() over (order by jq.Id) as row_num from [{schemaName}].JobQueue jq with (nolock, forceseek) where jq.Queue = @queue and jq.FetchedAt is not null ) as r where r.row_num between @start and @end"); return connection.Query( query, new { queue = ctx.Queue, start = ctx.From + 1, end = ctx.From + ctx.PerPage }) .ToList() .Select(static x => x.JobId) .ToList(); }, new QueuePageQueryContext(queue, from, perPage)); } private readonly struct QueuePageQueryContext(string queue, int from, int perPage) { public string Queue { get; } = queue; public int From { get; } = from; public int PerPage { get; } = perPage; } public EnqueuedAndFetchedCountDto GetEnqueuedAndFetchedCount(string queue) { return _storage.UseConnection(null, static (storage, connection, q) => { var query = storage.GetQueryFromTemplate(static schemaName => $@" select sum(Enqueued) as EnqueuedCount, sum(Fetched) as FetchedCount from ( select case when FetchedAt is null then 1 else 0 end as Enqueued, case when FetchedAt is not null then 1 else 0 end as Fetched from [{schemaName}].JobQueue with (nolock, forceseek) where Queue = @queue ) q"); var result = connection.QuerySingle(query, new { queue = q }); return new EnqueuedAndFetchedCountDto { EnqueuedCount = result.EnqueuedCount, FetchedCount = result.FetchedCount }; }, queue); } private sealed class JobIdDto { [UsedImplicitly] public long JobId { get; set; } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerJobQueueProvider.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; namespace Hangfire.SqlServer { internal sealed class SqlServerJobQueueProvider : IPersistentJobQueueProvider { private readonly IPersistentJobQueue _jobQueue; private readonly IPersistentJobQueueMonitoringApi _monitoringApi; public SqlServerJobQueueProvider([NotNull] SqlServerStorage storage, [NotNull] SqlServerStorageOptions options) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (options == null) throw new ArgumentNullException(nameof(options)); _jobQueue = new SqlServerJobQueue(storage, options); _monitoringApi = new SqlServerJobQueueMonitoringApi(storage); } public IPersistentJobQueue GetJobQueue() { return _jobQueue; } public IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi() { return _monitoringApi; } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerMonitoringApi.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Data.Common; using System.Globalization; using System.Linq; using Dapper; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.SqlServer.Entities; using Hangfire.States; using Hangfire.Storage; using Hangfire.Storage.Monitoring; // ReSharper disable RedundantAnonymousTypePropertyName namespace Hangfire.SqlServer { internal sealed class SqlServerMonitoringApi : JobStorageMonitor { private readonly SqlServerStorage _storage; private readonly int? _jobListLimit; public SqlServerMonitoringApi([NotNull] SqlServerStorage storage, int? jobListLimit) { if (storage == null) throw new ArgumentNullException(nameof(storage)); _storage = storage; _jobListLimit = jobListLimit; } public override long ScheduledCount() { return UseConnection(connection => GetNumberOfJobsByStateName(connection, ScheduledState.StateName)); } public override long EnqueuedCount(string queue) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var queueApi = GetQueueApi(queue); var counters = queueApi.GetEnqueuedAndFetchedCount(queue); return counters.EnqueuedCount ?? 0; } public override long FetchedCount(string queue) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var queueApi = GetQueueApi(queue); var counters = queueApi.GetEnqueuedAndFetchedCount(queue); return counters.FetchedCount ?? 0; } public override long FailedCount() { return UseConnection(connection => GetNumberOfJobsByStateName(connection, FailedState.StateName)); } public override long ProcessingCount() { return UseConnection(connection => GetNumberOfJobsByStateName(connection, ProcessingState.StateName)); } public override JobList ProcessingJobs(int @from, int count) { return UseConnection(connection => GetJobs( connection, from, count, ProcessingState.StateName, descending: false, static (sqlJob, job, invocationData, loadException, stateData) => new ProcessingJobDto { Job = job, LoadException = loadException, InvocationData = invocationData, InProcessingState = ProcessingState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase), ServerId = stateData.TryGetValue("ServerId", out var serverId) ? serverId : stateData["ServerName"], StartedAt = sqlJob.StateChanged, StateData = stateData })); } public override JobList ScheduledJobs(int @from, int count) { return UseConnection(connection => GetJobs( connection, from, count, ScheduledState.StateName, descending: false, static (sqlJob, job, invocationData, loadException, stateData) => new ScheduledJobDto { Job = job, LoadException = loadException, InvocationData = invocationData, InScheduledState = ScheduledState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase), EnqueueAt = JobHelper.DeserializeNullableDateTime(stateData["EnqueueAt"]) ?? DateTime.MinValue, ScheduledAt = sqlJob.StateChanged, StateData = stateData })); } public override IDictionary SucceededByDatesCount() { return UseConnection(connection => GetTimelineStats(connection, "succeeded")); } public override IDictionary FailedByDatesCount() { return UseConnection(connection => GetTimelineStats(connection, "failed")); } public override IDictionary DeletedByDatesCount() { return UseConnection(connection => GetTimelineStats(connection, "deleted")); } public override IList Servers() { return UseConnection>(connection => { var servers = connection.Query( _storage.GetQueryFromTemplate(static schemaName => $@"select * from [{schemaName}].Server with (nolock)"), commandTimeout: _storage.CommandTimeout) .ToList(); var result = new List(); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var server in servers) { var data = SerializationHelper.Deserialize(server.Data); if (data.Queues == null && data.StartedAt == null && data.WorkerCount == 0) { data = SerializationHelper.Deserialize(server.Data, SerializationOption.User); } result.Add(new ServerDto { Name = server.Id, Heartbeat = server.LastHeartbeat, Queues = data.Queues, StartedAt = data.StartedAt ?? DateTime.MinValue, WorkersCount = data.WorkerCount }); } return result; }); } public override JobList FailedJobs(int @from, int count) { return UseConnection(connection => GetJobs( connection, from, count, FailedState.StateName, descending: true, static (sqlJob, job, invocationData, loadException, stateData) => new FailedJobDto { Job = job, LoadException = loadException, InvocationData = invocationData, InFailedState = FailedState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase), Reason = sqlJob.StateReason, ExceptionDetails = stateData["ExceptionDetails"], ExceptionMessage = stateData["ExceptionMessage"], ExceptionType = stateData["ExceptionType"], FailedAt = sqlJob.StateChanged, StateData = stateData })); } public override JobList SucceededJobs(int @from, int count) { return UseConnection(connection => GetJobs( connection, from, count, SucceededState.StateName, descending: true, static (sqlJob, job, invocationData, loadException, stateData) => new SucceededJobDto { Job = job, LoadException = loadException, InvocationData = invocationData, InSucceededState = SucceededState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase), Result = stateData["Result"], TotalDuration = stateData.TryGetValue("PerformanceDuration", out var duration) && stateData.TryGetValue("Latency", out var latency) ? (long?)long.Parse(duration, CultureInfo.InvariantCulture) + (long?)long.Parse(latency, CultureInfo.InvariantCulture) : null, SucceededAt = sqlJob.StateChanged, StateData = stateData })); } public override JobList DeletedJobs(int @from, int count) { return UseConnection(connection => GetJobs( connection, from, count, DeletedState.StateName, descending: true, static (sqlJob, job, invocationData, loadException, stateData) => new DeletedJobDto { Job = job, LoadException = loadException, InvocationData = invocationData, InDeletedState = DeletedState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase), DeletedAt = sqlJob.StateChanged, StateData = stateData })); } public override JobList AwaitingJobs(int @from, int count) { var awaitingJobs = UseConnection(connection => GetJobs( connection, from, count, AwaitingState.StateName, descending: false, static (sqlJob, job, invocationData, loadException, stateData) => new AwaitingJobDto { Job = job, LoadException = loadException, InvocationData = invocationData, InAwaitingState = AwaitingState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase), AwaitingAt = sqlJob.StateChanged, StateData = stateData })); var parentIds = awaitingJobs .Where(static x => x.Value != null && x.Value.InAwaitingState && x.Value.StateData.ContainsKey("ParentId")) .Select(static x => long.Parse(x.Value.StateData["ParentId"], CultureInfo.InvariantCulture)) .ToArray(); var parentStates = UseConnection(connection => { return connection.Query( _storage.GetQueryFromTemplate(static schemaName => $@"select Id, StateName from [{schemaName}].Job with (nolock, forceseek) where Id in @ids"), new { ids = parentIds }, commandTimeout: _storage.CommandTimeout) .ToDictionary(static x => x.Id, static x => x.StateName); }); foreach (var awaitingJob in awaitingJobs) { if (awaitingJob.Value != null && awaitingJob.Value.InAwaitingState && awaitingJob.Value.StateData.TryGetValue("ParentId", out var parentIdString)) { var parentId = long.Parse(parentIdString, CultureInfo.InvariantCulture); if (parentStates.TryGetValue(parentId, out var parentStateName)) { awaitingJob.Value.ParentStateName = parentStateName; } } } return awaitingJobs; } public override long AwaitingCount() { return UseConnection(connection => GetNumberOfJobsByStateName(connection, AwaitingState.StateName)); } public override IList Queues() { var tuples = _storage.QueueProviders .Select(static x => x.GetJobQueueMonitoringApi()) .SelectMany(static x => x.GetQueues(), static (monitoring, queue) => new { Monitoring = monitoring, Queue = queue }) .OrderBy(static x => x.Queue) .ToArray(); var result = new List(tuples.Length); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var tuple in tuples) { var enqueuedJobIds = tuple.Monitoring.GetEnqueuedJobIds(tuple.Queue, 0, 5); var counters = tuple.Monitoring.GetEnqueuedAndFetchedCount(tuple.Queue); var firstJobs = UseConnection(connection => EnqueuedJobs(connection, enqueuedJobIds.ToArray())); result.Add(new QueueWithTopEnqueuedJobsDto { Name = tuple.Queue, Length = counters.EnqueuedCount ?? 0, Fetched = counters.FetchedCount, FirstJobs = firstJobs }); } return result; } public override JobList EnqueuedJobs(string queue, int from, int perPage) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var queueApi = GetQueueApi(queue); var enqueuedJobIds = queueApi.GetEnqueuedJobIds(queue, from, perPage); return UseConnection(connection => EnqueuedJobs(connection, enqueuedJobIds.ToArray())); } public override JobList FetchedJobs(string queue, int @from, int perPage) { if (queue == null) throw new ArgumentNullException(nameof(queue)); var queueApi = GetQueueApi(queue); var fetchedJobIds = queueApi.GetFetchedJobIds(queue, from, perPage); return UseConnection(connection => FetchedJobs(connection, fetchedJobIds.ToArray())); } public override IDictionary HourlySucceededJobs() { return UseConnection(connection => GetHourlyTimelineStats(connection, "succeeded")); } public override IDictionary HourlyFailedJobs() { return UseConnection(connection => GetHourlyTimelineStats(connection, "failed")); } public override IDictionary HourlyDeletedJobs() { return UseConnection(connection => GetHourlyTimelineStats(connection, "deleted")); } public override JobDetailsDto JobDetails(string jobId) { if (jobId == null) throw new ArgumentNullException(nameof(jobId)); return UseConnection(connection => { var query = _storage.GetQueryFromTemplate(static schemaName => $@" select * from [{schemaName}].Job with (nolock, forceseek) where Id = @id select * from [{schemaName}].JobParameter with (nolock, forceseek) where JobId = @id select * from [{schemaName}].State with (nolock, forceseek) where JobId = @id order by Id desc"); using (var multi = connection.QueryMultiple(query, new { id = jobId }, commandTimeout: _storage.CommandTimeout)) { var job = multi.ReadSingleOrDefault(); if (job == null) return null; var parameters = multi.Read() .GroupBy(static x => x.Name) .Select(static grp => grp.First()) .ToDictionary(static x => x.Name, static x => x.Value); var deserializedJob = DeserializeJob(job.InvocationData, job.Arguments, out var payload, out var exception); if (deserializedJob == null) { if (payload != null) { parameters.Add("DBG_Type", payload.Type); parameters.Add("DBG_Method", payload.Method); parameters.Add("DBG_Args", payload.Arguments); } else { parameters.Add("DBG_Payload", job.InvocationData); parameters.Add("DBG_Args", job.Arguments); } } if (exception != null) { parameters.Add("DBG_Exception", (exception.InnerException ?? exception).Message); } var history = multi.Read() .ToList() .Select(static x => new StateHistoryDto { StateName = x.Name, CreatedAt = x.CreatedAt, Reason = x.Reason, Data = new SafeDictionary( SerializationHelper.Deserialize>(x.Data), StringComparer.OrdinalIgnoreCase), }) .ToList(); return new JobDetailsDto { CreatedAt = job.CreatedAt, ExpireAt = job.ExpireAt, Job = deserializedJob, InvocationData = payload, LoadException = exception, History = history, Properties = parameters }; } }); } public override long SucceededListCount() { return UseConnection(connection => GetNumberOfJobsByStateName(connection, SucceededState.StateName)); } public override long DeletedListCount() { return UseConnection(connection => GetNumberOfJobsByStateName(connection, DeletedState.StateName)); } public override StatisticsDto GetStatistics() { var query = _storage.GetQueryFromTemplate(static schemaName => $@" set transaction isolation level read committed; select count(Id) from [{schemaName}].Job with (nolock, forceseek) where StateName = N'Enqueued'; select count(Id) from [{schemaName}].Job with (nolock, forceseek) where StateName = N'Failed'; select count(Id) from [{schemaName}].Job with (nolock, forceseek) where StateName = N'Processing'; select count(Id) from [{schemaName}].Job with (nolock, forceseek) where StateName = N'Scheduled'; select count(Id) from [{schemaName}].Job with (nolock, forceseek) where StateName = N'Awaiting'; select count(Id) from [{schemaName}].Server with (nolock); select sum(s.[Value]) from ( select sum([Value]) as [Value] from [{schemaName}].Counter with (nolock, forceseek) where [Key] = N'stats:succeeded' union all select [Value] from [{schemaName}].AggregatedCounter with (nolock, forceseek) where [Key] = N'stats:succeeded' ) as s; select sum(s.[Value]) from ( select sum([Value]) as [Value] from [{schemaName}].Counter with (nolock, forceseek) where [Key] = N'stats:deleted' union all select [Value] from [{schemaName}].AggregatedCounter with (nolock, forceseek) where [Key] = N'stats:deleted' ) as s; select count(*) from [{schemaName}].[Set] with (nolock, forceseek) where [Key] = N'recurring-jobs'; select count(*) from [{schemaName}].[Set] with (nolock, forceseek) where [Key] = N'retries'; "); var statistics = UseConnection(connection => { var stats = new StatisticsDto(); using (var multi = connection.QueryMultiple(query, commandTimeout: _storage.CommandTimeout)) { stats.Enqueued = multi.ReadSingle(); stats.Failed = multi.ReadSingle(); stats.Processing = multi.ReadSingle(); stats.Scheduled = multi.ReadSingle(); stats.Awaiting = multi.ReadSingle(); stats.Servers = multi.ReadSingle(); stats.Succeeded = multi.ReadSingleOrDefault() ?? 0; stats.Deleted = multi.ReadSingleOrDefault() ?? 0; stats.Recurring = multi.ReadSingle(); stats.Retries = multi.ReadSingle(); } return stats; }); statistics.Queues = _storage.QueueProviders .SelectMany(static x => x.GetJobQueueMonitoringApi().GetQueues()) .Count(); return statistics; } private Dictionary GetHourlyTimelineStats(DbConnection connection, string type) { var endDate = DateTime.UtcNow; var dates = new List(); for (var i = 0; i < 24; i++) { dates.Add(endDate); endDate = endDate.AddHours(-1); } var keyMaps = dates.ToDictionary(x => $"stats:{type}:{x.ToString("yyyy-MM-dd-HH", CultureInfo.InvariantCulture)}", static x => x); return GetTimelineStats(connection, keyMaps); } private Dictionary GetTimelineStats(DbConnection connection, string type) { var endDate = DateTime.UtcNow.Date; var dates = new List(); for (var i = 0; i < 7; i++) { dates.Add(endDate); endDate = endDate.AddDays(-1); } var keyMaps = dates.ToDictionary(x => $"stats:{type}:{x.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}", static x => x); return GetTimelineStats(connection, keyMaps); } private Dictionary GetTimelineStats( DbConnection connection, IDictionary keyMaps) { var query = _storage.GetQueryFromTemplate(static schemaName => $@"select [Key], [Value] as [Count] from [{schemaName}].AggregatedCounter with (nolock, forceseek) where [Key] in @keys"); var valuesMap = connection.Query( query, new { keys = keyMaps.Keys }, commandTimeout: _storage.CommandTimeout) .ToDictionary(static x => (string)x.Key, static x => (long)x.Count); foreach (var key in keyMaps.Keys) { if (!valuesMap.ContainsKey(key)) valuesMap.Add(key, 0); } var result = new Dictionary(); for (var i = 0; i < keyMaps.Count; i++) { var value = valuesMap[keyMaps.ElementAt(i).Key]; result.Add(keyMaps.ElementAt(i).Value, value); } return result; } private IPersistentJobQueueMonitoringApi GetQueueApi(string queueName) { var provider = _storage.QueueProviders.GetProvider(queueName); var monitoringApi = provider.GetJobQueueMonitoringApi(); return monitoringApi; } private T UseConnection(Func action) { return _storage.UseConnection(null, static (_, connection, action) => action(connection), action); } private JobList EnqueuedJobs(DbConnection connection, long[] jobIds) { var query = _storage.GetQueryFromTemplate(static schemaName => $@"select j.*, s.Reason as StateReason, s.Data as StateData, s.CreatedAt as StateChanged from [{schemaName}].Job j with (nolock, forceseek) left join [{schemaName}].State s with (nolock, forceseek) on s.Id = j.StateId and s.JobId = j.Id where j.Id in @jobIds"); var jobs = connection.Query( query, new { jobIds = jobIds }, commandTimeout: _storage.CommandTimeout) .ToDictionary(static x => x.Id, static x => x); var sortedSqlJobs = jobIds .Select(jobId => jobs.TryGetValue(jobId, out var job) ? job : new SqlJob { Id = jobId }) .ToList(); return DeserializeJobs( sortedSqlJobs, static (sqlJob, job, invocationData, loadException, stateData) => new EnqueuedJobDto { Job = job, LoadException = loadException, InvocationData = invocationData, State = sqlJob.StateName, InEnqueuedState = EnqueuedState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase), EnqueuedAt = EnqueuedState.StateName.Equals(sqlJob.StateName, StringComparison.OrdinalIgnoreCase) ? sqlJob.StateChanged : null, StateData = stateData }); } private long GetNumberOfJobsByStateName(DbConnection connection, string stateName) { var query = _storage.GetQueryFromTemplate(_jobListLimit.HasValue ? static schemaName => $@"select count(j.Id) from (select top (@limit) Id from [{schemaName}].Job with (nolock, forceseek) where StateName = @state) as j" : static schemaName => $@"select count(Id) from [{schemaName}].Job with (nolock, forceseek) where StateName = @state"); var count = connection.ExecuteScalar( query, new { state = stateName, limit = _jobListLimit }, commandTimeout: _storage.CommandTimeout); return count; } private static Job DeserializeJob(string invocationData, string arguments, out InvocationData data, out JobLoadException exception) { data = InvocationData.DeserializePayload(invocationData); if (!String.IsNullOrEmpty(arguments)) { data.Arguments = arguments; } try { exception = null; return data.DeserializeJob(); } catch (JobLoadException ex) { exception = ex; return null; } } private JobList GetJobs( DbConnection connection, int from, int count, string stateName, bool descending, Func, TDto> selector) { var order = descending ? "desc" : "asc"; var query = String.Format(CultureInfo.InvariantCulture, _storage.GetQueryFromTemplate(static schemaName => $@";with cte as ( select j.Id, row_number() over (order by j.Id {{0}}) as row_num from [{schemaName}].Job j with (nolock, forceseek) where j.StateName = @stateName ) select j.*, s.Reason as StateReason, s.Data as StateData, s.CreatedAt as StateChanged from [{schemaName}].Job j with (nolock, forceseek) inner join cte on cte.Id = j.Id left join [{schemaName}].State s with (nolock, forceseek) on j.StateId = s.Id and j.Id = s.JobId where cte.row_num between @start and @end"), order); var jobs = connection.Query( query, new { stateName = stateName, start = @from + 1, end = @from + count }, commandTimeout: _storage.CommandTimeout) .ToList(); return DeserializeJobs(jobs, selector); } private static JobList DeserializeJobs( ICollection jobs, Func, TDto> selector) { var result = new List>(jobs.Count); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var job in jobs) { var dto = default(TDto); if (job.InvocationData != null) { var deserializedData = SerializationHelper.Deserialize>(job.StateData); var stateData = deserializedData != null ? new SafeDictionary(deserializedData, StringComparer.OrdinalIgnoreCase) : null; dto = selector(job, DeserializeJob(job.InvocationData, job.Arguments, out var invocationData, out var loadException), invocationData, loadException, stateData); } result.Add(new KeyValuePair( job.Id.ToString(CultureInfo.InvariantCulture), dto)); } return new JobList(result); } private JobList FetchedJobs(DbConnection connection, IEnumerable jobIds) { var query = _storage.GetQueryFromTemplate(static schemaName => $@"select j.*, s.Reason as StateReason, s.Data as StateData from [{schemaName}].Job j with (nolock, forceseek) left join [{schemaName}].State s with (nolock, forceseek) on s.Id = j.StateId and s.JobId = j.Id where j.Id in @jobIds"); var jobs = connection.Query( query, new { jobIds = jobIds }, commandTimeout: _storage.CommandTimeout) .ToList(); var result = new List>(jobs.Count); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var job in jobs) { result.Add(new KeyValuePair( job.Id.ToString(CultureInfo.InvariantCulture), new FetchedJobDto { Job = DeserializeJob(job.InvocationData, job.Arguments, out _, out _), State = job.StateName, })); } return new JobList(result); } /// /// Overloaded dictionary that doesn't throw if given an invalid key /// Fixes issues such as https://github.com/HangfireIO/Hangfire/issues/871 /// private sealed class SafeDictionary : Dictionary { public SafeDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } public new TValue this[TKey i] { get => TryGetValue(i, out var value) ? value : default(TValue); set => base[i] = value; } } private sealed class ParentStateDto { public long Id { get; set; } public string StateName { get; set; } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerObjectsInstaller.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Data.Common; using System.IO; using System.Reflection; using Dapper; using Hangfire.Logging; namespace Hangfire.SqlServer { public static class SqlServerObjectsInstaller { [Obsolete("This field is unused and will be removed in 2.0.0.")] public static readonly int RequiredSchemaVersion = 5; public static readonly int LatestSchemaVersion = 9; public static void Install(DbConnection connection) { Install(connection, null); } public static void Install(DbConnection connection, string schema) { Install(connection, schema, false); } public static void Install(DbConnection connection, string schema, bool enableHeavyMigrations) { if (connection == null) throw new ArgumentNullException(nameof(connection)); var script = GetInstallScript(schema, enableHeavyMigrations); connection.Execute(script, commandTimeout: 0); } public static string GetInstallScript(string schema, bool enableHeavyMigrations) { var script = GetStringResource( typeof(SqlServerObjectsInstaller).GetTypeInfo().Assembly, "Hangfire.SqlServer.Install.sql"); script = script.Replace("$(HangFireSchema)", !string.IsNullOrWhiteSpace(schema) ? schema : Constants.DefaultSchema); if (!enableHeavyMigrations) { script = script.Replace("--SET @DISABLE_HEAVY_MIGRATIONS = 1;", "SET @DISABLE_HEAVY_MIGRATIONS = 1;"); } return script; } private static string GetStringResource(Assembly assembly, string resourceName) { using (var stream = assembly.GetManifestResourceStream(resourceName)) { if (stream == null) { throw new InvalidOperationException( $"Requested resource `{resourceName}` was not found in the assembly `{assembly}`."); } using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); } } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerStorage.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using System.Text; #if FEATURE_CONFIGURATIONMANAGER using System.Configuration; #endif #if FEATURE_TRANSACTIONSCOPE using System.Transactions; using IsolationLevel = System.Transactions.IsolationLevel; #endif using Dapper; using Hangfire.Annotations; using Hangfire.Dashboard; using Hangfire.Logging; using Hangfire.Server; using Hangfire.Storage; namespace Hangfire.SqlServer { public class SqlServerStorage : JobStorage { private static readonly char[] SemicolonSeparator = new[] { ';' }; private static readonly char[] EqualSignSeparator = new[] { '=' }; private readonly ConcurrentDictionary, string>, string> _queryTemplateCache = new(new QueryTemplateKeyEqualityComparer()); private readonly DbConnection _existingConnection; private readonly Func _connectionFactory; private readonly SqlServerStorageOptions _options; private readonly string _connectionString; private string _escapedSchemaName; private SqlServerHeartbeatProcess _heartbeatProcess; private readonly Dictionary _features = new Dictionary(StringComparer.OrdinalIgnoreCase) { { JobStorageFeatures.ExtendedApi, true }, { JobStorageFeatures.JobQueueProperty, true }, { JobStorageFeatures.ProcessesInsteadOfComponents, true }, { JobStorageFeatures.Connection.BatchedGetFirstByLowest, true }, { JobStorageFeatures.Connection.GetUtcDateTime, true }, { JobStorageFeatures.Connection.GetSetContains, true }, { JobStorageFeatures.Connection.LimitedGetSetCount, true }, { JobStorageFeatures.Transaction.AcquireDistributedLock, true }, { JobStorageFeatures.Transaction.CreateJob, false }, { JobStorageFeatures.Transaction.SetJobParameter, false }, { JobStorageFeatures.Monitoring.DeletedStateGraphs, true }, { JobStorageFeatures.Monitoring.AwaitingJobs, true } }; public SqlServerStorage(string nameOrConnectionString) : this(nameOrConnectionString, new SqlServerStorageOptions()) { } /// /// Initializes SqlServerStorage from the provided SqlServerStorageOptions and either the provided connection /// string or the connection string with provided name pulled from the application config file. /// /// Either a SQL Server connection string or the name of /// a SQL Server connection string located in the connectionStrings node in the application config /// /// argument is null. /// argument is null. /// argument is neither /// a valid SQL Server connection string nor the name of a connection string in the application /// config file. public SqlServerStorage(string nameOrConnectionString, SqlServerStorageOptions options) { if (nameOrConnectionString == null) throw new ArgumentNullException(nameof(nameOrConnectionString)); if (options == null) throw new ArgumentNullException(nameof(options)); _connectionString = GetConnectionString(nameOrConnectionString); _connectionFactory = DefaultConnectionFactory; _options = options; Initialize(); } /// /// Initializes a new instance of the class with /// explicit instance of the class that will be used /// to query the data. /// public SqlServerStorage([NotNull] DbConnection existingConnection) : this(existingConnection, new SqlServerStorageOptions()) { } /// /// Initializes a new instance of the class with /// explicit instance of the class that will be used /// to query the data, with the given options. /// public SqlServerStorage([NotNull] DbConnection existingConnection, [NotNull] SqlServerStorageOptions options) { if (existingConnection == null) throw new ArgumentNullException(nameof(existingConnection)); if (options == null) throw new ArgumentNullException(nameof(options)); _existingConnection = existingConnection; _options = options; Initialize(); } /// /// Initializes a new instance of the class with /// a connection factory class that will be invoked /// to create new database connections for querying the data. /// public SqlServerStorage([NotNull] Func connectionFactory) : this(connectionFactory, new SqlServerStorageOptions()) { } /// /// Initializes a new instance of the class with /// a connection factory class that will be invoked /// to create new database connections for querying the data. /// public SqlServerStorage([NotNull] Func connectionFactory, [NotNull] SqlServerStorageOptions options) { if (connectionFactory == null) throw new ArgumentNullException(nameof(connectionFactory)); if (options == null) throw new ArgumentNullException(nameof(options)); _connectionFactory = connectionFactory; _options = options; Initialize(); } public virtual PersistentJobQueueProviderCollection QueueProviders { get; private set; } public override bool LinearizableReads => true; internal string SchemaName => _escapedSchemaName; internal int? CommandTimeout => _options.CommandTimeout.HasValue ? (int)_options.CommandTimeout.Value.TotalSeconds : (int?)null; internal int? CommandBatchMaxTimeout => _options.CommandBatchMaxTimeout.HasValue ? (int)_options.CommandBatchMaxTimeout.Value.TotalSeconds : (int?)null; internal TimeSpan? SlidingInvisibilityTimeout => _options.SlidingInvisibilityTimeout; internal SqlServerStorageOptions Options => _options; internal SqlServerHeartbeatProcess HeartbeatProcess => _heartbeatProcess; public override IMonitoringApi GetMonitoringApi() { return new SqlServerMonitoringApi(this, _options.DashboardJobListLimit); } public override IStorageConnection GetConnection() { return new SqlServerConnection(this); } #pragma warning disable 618 [Obsolete($"Please use the `{nameof(GetStorageWideProcesses)}` and/or `{nameof(GetServerRequiredProcesses)}` methods instead, and enable `{nameof(JobStorageFeatures)}.{nameof(JobStorageFeatures.ProcessesInsteadOfComponents)}`. Will be removed in 2.0.0.")] public override IEnumerable GetComponents() #pragma warning restore 618 { yield return new ExpirationManager(this, _options.InactiveStateExpirationTimeout, _options.JobExpirationCheckInterval); yield return new CountersAggregator(this, _options.CountersAggregateInterval); yield return _heartbeatProcess; } public override IEnumerable GetServerRequiredProcesses() { yield return _heartbeatProcess; } public override IEnumerable GetStorageWideProcesses() { yield return new ExpirationManager(this, _options.InactiveStateExpirationTimeout, _options.JobExpirationCheckInterval); yield return new CountersAggregator(this, _options.CountersAggregateInterval); } public override void WriteOptionsToLog(ILog logger) { logger.Info($"Using the following options for SQL Server job storage: Queue poll interval: {_options.QueuePollInterval}."); } public override bool HasFeature(string featureId) { if (featureId == null) throw new ArgumentNullException(nameof(featureId)); return _features.TryGetValue(featureId, out var isSupported) ? isSupported : base.HasFeature(featureId); } public override string ToString() { const string canNotParseMessage = ""; try { if (_connectionString == null) { return "SQL Server (custom)"; } var parts = _connectionString.Split(SemicolonSeparator, StringSplitOptions.RemoveEmptyEntries) .Select(static x => x.Split(EqualSignSeparator, StringSplitOptions.RemoveEmptyEntries)) .Select(static x => new { Key = x[0].Trim(), Value = x[1].Trim() }) .GroupBy(static x => x.Key, StringComparer.OrdinalIgnoreCase) .ToDictionary(static x => x.Key, static x => x.Last().Value, StringComparer.OrdinalIgnoreCase); var builder = new StringBuilder(); foreach (var alias in new[] { "Data Source", "Server", "Address", "Addr", "Network Address" }) { if (parts.TryGetValue(alias, out var part)) { builder.Append(part); break; } } if (builder.Length != 0) builder.Append('@'); foreach (var alias in new[] { "Database", "Initial Catalog" }) { if (parts.TryGetValue(alias, out var part)) { builder.Append(part); break; } } return builder.Length != 0 ? $"SQL Server: {builder}" : canNotParseMessage; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { return canNotParseMessage; } } internal string GetQueryFromTemplate(Func templateFunc) { return _queryTemplateCache.GetOrAdd( new KeyValuePair, string>(templateFunc, SchemaName), static pair => pair.Key(pair.Value)); } internal void UseConnection( DbConnection dedicatedConnection, [InstantHandle] Action action) { UseConnection(dedicatedConnection, static (storage, connection, ctx) => { ctx(storage, connection); return true; }, action); } internal TResult UseConnection( DbConnection dedicatedConnection, [InstantHandle] Func action) { return UseConnection(dedicatedConnection, static (storage, connection, ctx) => ctx(storage, connection), action); } internal TResult UseConnection( DbConnection dedicatedConnection, [InstantHandle] Func func, TContext context) { DbConnection connection = null; try { connection = dedicatedConnection ?? CreateAndOpenConnection(); return func(this, connection, context); } finally { if (dedicatedConnection == null) { ReleaseConnection(connection); } } } internal void UseTransaction( DbConnection dedicatedConnection, [InstantHandle] Action action, TContext context) { UseTransaction(dedicatedConnection, static (storage, connection, transaction, ctx) => { ctx.Key(storage, connection, transaction, ctx.Value); return true; }, new KeyValuePair, TContext>(action, context), null); } internal TResult UseTransaction( DbConnection dedicatedConnection, [InstantHandle] Func func, TContext context, IsolationLevel? isolationLevel) { isolationLevel = isolationLevel ?? (_options.UseRecommendedIsolationLevel ? IsolationLevel.ReadCommitted #pragma warning disable 618 : _options.TransactionIsolationLevel); #pragma warning restore 618 #if FEATURE_TRANSACTIONSCOPE if (IsRunningOnWindows() && !_options.DisableTransactionScope) { using (var transaction = CreateTransaction(isolationLevel)) { var result = UseConnection(dedicatedConnection, static (storage, connection, ctx) => { connection.EnlistTransaction(Transaction.Current); return ctx.Key(storage, connection, null, ctx.Value); }, new KeyValuePair, TContext>(func, context)); transaction.Complete(); return result; } } else #endif { return UseConnection(dedicatedConnection, static (storage, connection, ctx) => { using (var transaction = connection.BeginTransaction( #if !FEATURE_TRANSACTIONSCOPE ctx.Value.Value ?? #endif System.Data.IsolationLevel.ReadCommitted)) { TResult result; try { result = ctx.Key(storage, connection, transaction, ctx.Value.Key); transaction.Commit(); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // It is possible that XACT_ABORT option is set, and in this // case transaction will be aborted automatically on server. // Some older SqlClient implementations throw InvalidOperationException // when trying to rollback such an aborted transaction, so we // try to handle this case. // // It's also possible that our connection is broken, so this // check is useful even when XACT_ABORT option wasn't set. if (transaction.Connection != null) { // Don't rely on implicit rollback when calling the Dispose // method, because some implementations may throw the // NullReferenceException, although it's prohibited to throw // any exception from a Dispose method, according to the // .NET Framework Design Guidelines: // https://github.com/dotnet/efcore/issues/12864 // https://github.com/HangfireIO/Hangfire/issues/1494 transaction.Rollback(); } throw; } return result; } }, new KeyValuePair, KeyValuePair>( func, new KeyValuePair(context, isolationLevel))); } } internal DbConnection CreateAndOpenConnection() { using (_options.ImpersonationFunc?.Invoke()) { DbConnection connection = null; try { connection = _existingConnection ?? _connectionFactory(); if (connection.State == ConnectionState.Closed) { connection.Open(); } return connection; } catch (Exception ex) when (ex.IsCatchableExceptionType()) { ReleaseConnection(connection); throw; } } } internal bool IsExistingConnection(IDbConnection connection) { return connection != null && ReferenceEquals(connection, _existingConnection); } internal void ReleaseConnection(IDbConnection connection) { if (connection != null && !IsExistingConnection(connection)) { connection.Dispose(); } } private DbConnection DefaultConnectionFactory() { var connection = _options.SqlClientFactory.CreateConnection() ?? throw new InvalidOperationException($"The provider factory ({_options.SqlClientFactory}) returned a null DbConnection."); connection.ConnectionString = _connectionString; return connection; } private static bool IsRunningOnWindows() { #if !NETSTANDARD1_3 return Environment.OSVersion.Platform == PlatformID.Win32NT; #else return System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); #endif } private void Initialize() { _escapedSchemaName = _options.SchemaName.Replace("]", "]]"); if (_options.PrepareSchemaIfNecessary) { var log = LogProvider.GetLogger(typeof(SqlServerObjectsInstaller)); const int RetryAttempts = 3; log.Info("Start installing Hangfire SQL objects..."); Exception lastException = null; for (var i = 0; i < RetryAttempts; i++) { try { UseConnection(null, static (storage, connection) => { // TODO: Escape schema here??? SqlServerObjectsInstaller.Install(connection, storage.Options.SchemaName, storage.Options.EnableHeavyMigrations); }); lastException = null; break; } catch (DbException ex) { lastException = ex; log.WarnException("An exception occurred while trying to perform the migration." + (i < RetryAttempts - 1 ? " Retrying..." : ""), ex); } } if (lastException != null) { log.WarnException("Was unable to perform the Hangfire schema migration due to an exception. Ignore this message unless you've just installed or upgraded Hangfire.", lastException); } else { log.Info("Hangfire SQL objects installed."); } } if (_options.TryAutoDetectSchemaDependentOptions) { try { int? schema = UseConnection(null, static (storage, connection) => connection.ExecuteScalar($"select top (1) [Version] from [{storage.SchemaName}].[Schema]")); _options.UseRecommendedIsolationLevel = true; _options.UseIgnoreDupKeyOption = schema >= 8; _options.DisableGlobalLocks = schema >= 6; if (schema >= 6 && _options.DeleteExpiredBatchSize == -1) { _options.DeleteExpiredBatchSize = 10000; } } catch (Exception ex) { var log = LogProvider.GetLogger(typeof(SqlServerStorage)); log.ErrorException("Was unable to use the TryAutoDetectSchemaDependentOptions option due to an exception.", ex); } } InitializeQueueProviders(); _heartbeatProcess = new SqlServerHeartbeatProcess(); _features.Add( JobStorageFeatures.Transaction.RemoveFromQueue(typeof(SqlServerTimeoutJob)), _options.UseTransactionalAcknowledge); } private void InitializeQueueProviders() { var defaultQueueProvider = _options.DefaultQueueProvider ?? new SqlServerJobQueueProvider(this, _options); QueueProviders = new PersistentJobQueueProviderCollection(defaultQueueProvider); } private static string GetConnectionString(string nameOrConnectionString) { #if FEATURE_CONFIGURATIONMANAGER if (IsConnectionString(nameOrConnectionString)) { return nameOrConnectionString; } if (IsConnectionStringInConfiguration(nameOrConnectionString)) { return ConfigurationManager.ConnectionStrings[nameOrConnectionString].ConnectionString; } throw new ArgumentException( $"Could not find connection string with name '{nameOrConnectionString}' in application config file"); #else return nameOrConnectionString; #endif } #if FEATURE_CONFIGURATIONMANAGER private static bool IsConnectionString(string nameOrConnectionString) { return nameOrConnectionString.Contains(";"); } private static bool IsConnectionStringInConfiguration(string connectionStringName) { var connectionStringSetting = ConfigurationManager.ConnectionStrings[connectionStringName]; return connectionStringSetting != null; } #endif #if FEATURE_TRANSACTIONSCOPE private TransactionScope CreateTransaction(IsolationLevel? isolationLevel) { return isolationLevel != null ? new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = isolationLevel.Value, Timeout = _options.TransactionTimeout }) : new TransactionScope(); } #endif public static readonly DashboardMetric ActiveConnections = new DashboardMetric( "connections:active", "Metrics_ActiveConnections", static page => { var sqlStorage = page.Storage as SqlServerStorage; if (sqlStorage == null) return new Metric("???"); return sqlStorage.UseConnection(null, static (_, connection) => { var sqlQuery = @" select count(*) from sys.sysprocesses where dbid = db_id(@name) and status != 'background' and status != 'sleeping'"; var value = connection .QuerySingle(sqlQuery, new { name = connection.Database }); return new Metric(value); }); }); public static readonly DashboardMetric TotalConnections = new DashboardMetric( "connections:total", "Metrics_TotalConnections", static page => { var sqlStorage = page.Storage as SqlServerStorage; if (sqlStorage == null) return new Metric("???"); return sqlStorage.UseConnection(null, static (_, connection) => { var sqlQuery = @" select count(*) from sys.sysprocesses where dbid = db_id(@name) and status != 'background'"; var value = connection .QuerySingle(sqlQuery, new { name = connection.Database }); return new Metric(value); }); }); public static readonly DashboardMetric ActiveTransactions = new DashboardMetric( "transactions:active", "Metrics_SQLServer_ActiveTransactions", static page => { var sqlStorage = page.Storage as SqlServerStorage; if (sqlStorage == null) return new Metric("???"); return sqlStorage.UseConnection(null, static (_, connection) => { var sqlQuery = @" select count(*) from sys.sysprocesses where dbid = db_id(@name) and status != 'background' and open_tran = 1"; var value = connection .QuerySingle(sqlQuery, new { name = connection.Database }); return new Metric(value); }); }); public static readonly DashboardMetric DataFilesSize = new DashboardMetric( "database:files:rows:size", "Metrics_SQLServer_DataFilesSize", static page => { var sqlStorage = page.Storage as SqlServerStorage; if (sqlStorage == null) return new Metric("???"); return sqlStorage.UseConnection(null, static (_, connection) => { var sqlQuery = @" select SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT)/128.0) as RowsSizeMB from sys.database_files where type = 0;"; var value = connection.QuerySingle(sqlQuery); return new Metric(value.ToString("F", CultureInfo.CurrentCulture)); }); }); public static readonly DashboardMetric LogFilesSize = new DashboardMetric( "database:files:log:size", "Metrics_SQLServer_LogFilesSize", static page => { var sqlStorage = page.Storage as SqlServerStorage; if (sqlStorage == null) return new Metric("???"); return sqlStorage.UseConnection(null, static (_, connection) => { var sqlQuery = @" select SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS INT)/128.0) as LogSizeMB from sys.database_files where type = 1;"; var value = connection.QuerySingle(sqlQuery); return new Metric(value.ToString("F", CultureInfo.CurrentCulture)); }); }); public static readonly DashboardMetric SchemaVersion = new DashboardMetric( "sqlserver:schema", "Metrics_SQLServer_SchemaVersion", static page => { var sqlStorage = page.Storage as SqlServerStorage; if (sqlStorage == null) return new Metric("???"); return sqlStorage.UseConnection(null, static (storage, connection) => { var sqlQuery = $@"select top(1) [Version] from [{storage.SchemaName}].[Schema]"; var version = connection.QuerySingleOrDefault(sqlQuery); if (!version.HasValue) { return new Metric("Unspecified") { Style = MetricStyle.Danger, }; } return new Metric(version.Value) { Style = version < SqlServerObjectsInstaller.LatestSchemaVersion ? MetricStyle.Warning : version == SqlServerObjectsInstaller.LatestSchemaVersion ? MetricStyle.Success : MetricStyle.Default }; }); }); public static readonly Func PerformanceCounterDatabaseMetric = static (string objectName, string counterName, string instanceName) => new DashboardMetric( $"sqlserver:counter:{objectName}:{counterName}:{instanceName ?? "db"}", counterName, page => { if (objectName == null) throw new ArgumentNullException(nameof(objectName)); if (counterName == null) throw new ArgumentNullException(nameof(counterName)); var sqlStorage = page.Storage as SqlServerStorage; if (sqlStorage == null) return new Metric("???"); return sqlStorage.UseConnection(null, static (_, connection, ctx) => { var sqlQuery = $@"SELECT cntr_value FROM sys.dm_os_performance_counters where object_name = @objectName and instance_name = @instanceName and counter_name = @counterName"; long? value; try { value = connection.QuerySingle(sqlQuery, new { objectName = ctx.Item1, instanceName = ctx.Item2 ?? connection.Database, counterName = ctx.Item3 }); } catch { value = null; } return value != null ? new Metric(value.Value) : new Metric("???"); }, Tuple.Create(objectName, instanceName, counterName)); }); private sealed class QueryTemplateKeyEqualityComparer : IEqualityComparer, string>> { public bool Equals(KeyValuePair, string> x, KeyValuePair, string> y) { return x.Key.Equals(y.Key) && x.Value == y.Value; } public int GetHashCode(KeyValuePair, string> obj) { unchecked { return (obj.Key.GetHashCode() * 397) ^ obj.Value.GetHashCode(); } } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerStorageExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Data.Common; using System.Threading; using Hangfire.Annotations; using Hangfire.SqlServer; // ReSharper disable once CheckNamespace namespace Hangfire { public static class SqlServerStorageExtensions { private static int _metricsInitialized; public static IGlobalConfiguration UseSqlServerStorage( [NotNull] this IGlobalConfiguration configuration, [NotNull] string nameOrConnectionString) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (nameOrConnectionString == null) throw new ArgumentNullException(nameof(nameOrConnectionString)); var storage = new SqlServerStorage(nameOrConnectionString); return configuration.UseStorage(storage).UseSqlServerStorageCommonMetrics(); } public static IGlobalConfiguration UseSqlServerStorage( [NotNull] this IGlobalConfiguration configuration, [NotNull] string nameOrConnectionString, [NotNull] SqlServerStorageOptions options) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (nameOrConnectionString == null) throw new ArgumentNullException(nameof(nameOrConnectionString)); if (options == null) throw new ArgumentNullException(nameof(options)); var storage = new SqlServerStorage(nameOrConnectionString, options); return configuration.UseStorage(storage).UseSqlServerStorageCommonMetrics(); } public static IGlobalConfiguration UseSqlServerStorage( [NotNull] this IGlobalConfiguration configuration, [NotNull] Func connectionFactory) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (connectionFactory == null) throw new ArgumentNullException(nameof(connectionFactory)); var storage = new SqlServerStorage(connectionFactory); return configuration.UseStorage(storage).UseSqlServerStorageCommonMetrics(); } public static IGlobalConfiguration UseSqlServerStorage( [NotNull] this IGlobalConfiguration configuration, [NotNull] Func connectionFactory, [NotNull] SqlServerStorageOptions options) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (connectionFactory == null) throw new ArgumentNullException(nameof(connectionFactory)); if (options == null) throw new ArgumentNullException(nameof(options)); var storage = new SqlServerStorage(connectionFactory, options); return configuration.UseStorage(storage).UseSqlServerStorageCommonMetrics(); } private static IGlobalConfiguration UseSqlServerStorageCommonMetrics( [NotNull] this IGlobalConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (Interlocked.Exchange(ref _metricsInitialized, 1) == 0) { configuration.UseDashboardMetric(SqlServerStorage.SchemaVersion); configuration.UseDashboardMetric(SqlServerStorage.ActiveConnections); configuration.UseDashboardMetric(SqlServerStorage.TotalConnections); configuration.UseDashboardMetric(SqlServerStorage.ActiveTransactions); configuration.UseDashboardMetric(SqlServerStorage.DataFilesSize); configuration.UseDashboardMetric(SqlServerStorage.LogFilesSize); } return configuration; } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerStorageOptions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Data.Common; using System.Reflection; #if FEATURE_TRANSACTIONSCOPE using System.Transactions; #else using System.Data; #endif namespace Hangfire.SqlServer { public class SqlServerStorageOptions { private TimeSpan _queuePollInterval; private string _schemaName; private TimeSpan _jobExpirationCheckInterval; private TimeSpan? _slidingInvisibilityTimeout; private DbProviderFactory _sqlClientFactory; public SqlServerStorageOptions() { QueuePollInterval = TimeSpan.Zero; SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5); #pragma warning disable 618 InvisibilityTimeout = TimeSpan.FromMinutes(30); #pragma warning restore 618 JobExpirationCheckInterval = TimeSpan.FromMinutes(30); CountersAggregateInterval = TimeSpan.FromMinutes(5); PrepareSchemaIfNecessary = true; DashboardJobListLimit = 10000; _schemaName = Constants.DefaultSchema; TransactionTimeout = TimeSpan.FromMinutes(1); DisableGlobalLocks = false; DeleteExpiredBatchSize = -1; UseTransactionalAcknowledge = false; UseRecommendedIsolationLevel = true; CommandBatchMaxTimeout = TimeSpan.FromMinutes(5); TryAutoDetectSchemaDependentOptions = true; _sqlClientFactory = GetDefaultSqlClientFactory(); InactiveStateExpirationTimeout = TimeSpan.Zero; } private static DbProviderFactory GetDefaultSqlClientFactory() { var dbProviderFactoryTypes = new[] { "Microsoft.Data.SqlClient.SqlClientFactory, Microsoft.Data.SqlClient", // Available in the .NET Framework GAC, requires Version + Culture + PublicKeyToken to be explicitly specified "System.Data.SqlClient.SqlClientFactory, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Data.SqlClient.SqlClientFactory, System.Data.SqlClient", }; foreach (var dbProviderFactoryType in dbProviderFactoryTypes) { var providerFactoryType = Type.GetType(dbProviderFactoryType, throwOnError: false); if (providerFactoryType != null) { var instanceField = providerFactoryType.GetField("Instance"); if (instanceField == null) { continue; } var instance = (DbProviderFactory)instanceField.GetValue(null); if (instance != null) { return instance; } } } return null; } [Obsolete("TransactionIsolationLevel option is deprecated, please set UseRecommendedIsolationLevel instead. Will be removed in 2.0.0.")] public IsolationLevel? TransactionIsolationLevel { get; set; } public TimeSpan QueuePollInterval { get { return _queuePollInterval; } set { var message = $"The QueuePollInterval property value should be positive. Given: {value}."; if (value != value.Duration()) { throw new ArgumentException(message, nameof(value)); } _queuePollInterval = value; } } [Obsolete("Does not make sense anymore. Background jobs re-queued instantly even after ungraceful shutdown now. Will be removed in 2.0.0.")] public TimeSpan InvisibilityTimeout { get; set; } public TimeSpan? SlidingInvisibilityTimeout { get { return _slidingInvisibilityTimeout; } set { if (value <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(value), "Sliding timeout should be greater than zero"); } _slidingInvisibilityTimeout = value; } } public bool PrepareSchemaIfNecessary { get; set; } public TimeSpan JobExpirationCheckInterval { get { return _jobExpirationCheckInterval; } set { if (value.TotalMilliseconds > int.MaxValue) { throw new ArgumentOutOfRangeException(nameof(value), "Job expiration check interval cannot be greater than int.MaxValue"); } _jobExpirationCheckInterval = value; } } public TimeSpan CountersAggregateInterval { get; set; } public int? DashboardJobListLimit { get; set; } public TimeSpan TransactionTimeout { get; set; } public TimeSpan? CommandTimeout { get; set; } public TimeSpan? CommandBatchMaxTimeout { get; set; } public TimeSpan InactiveStateExpirationTimeout { get; set; } public string SchemaName { get { return _schemaName; } set { if (string.IsNullOrWhiteSpace(_schemaName)) { throw new ArgumentException(_schemaName, nameof(value)); } _schemaName = value; } } public Func ImpersonationFunc { get; set; } public bool DisableGlobalLocks { get; set; } [Obsolete("This option is deprecated and doesn't change anything. You can safely remove it. Will be removed in 2.0.0.")] public bool UsePageLocksOnDequeue { get; set; } public bool UseRecommendedIsolationLevel { get; set; } public bool EnableHeavyMigrations { get; set; } public bool UseFineGrainedLocks { get; set; } /// /// Gets or sets whether IGNORE_DUP_KEY was applied to [Hash] and [Set] tables and so MERGE /// statements can be replaced by much more efficient INSERT/UPDATE pair. This option allows /// to avoid deadlocks related to SERIALIZABLE-level range locks without introducing transient /// errors due to concurrency. /// public bool UseIgnoreDupKeyOption { get; set; } /// /// Gets or sets the number of records deleted in a single batch in expiration manager. Default /// value is 1000, but it can be configured to a higher one when processing throughput is high /// enough, so expiration manager becomes the bottleneck. /// public int DeleteExpiredBatchSize { get; set; } /// /// Gets or sets whether to enable experimental feature of transactional acknowledge of completed /// background jobs. In this case there will be less requests sent to SQL Server and better handling /// of data loss when asynchronous replication is used. But additional blocking on the JobQueue table /// is expected, since transaction commit requires an explicit Commit request to be sent. /// public bool UseTransactionalAcknowledge { get; set; } /// /// Gets or sets the for creating SqlConnection instances. /// Defaults to either System.Data.SqlClient.SqlClientFactory.Instance or /// Microsoft.Data.SqlClient.SqlClientFactory depending on which package reference exists /// on the consuming project. /// public DbProviderFactory SqlClientFactory { get => _sqlClientFactory ?? throw new InvalidOperationException("Please add a NuGet package reference to either 'Microsoft.Data.SqlClient' or 'System.Data.SqlClient' in your application project. " + "Hangfire.SqlServer supports both providers but let the consumer decide which one should be used."); set => _sqlClientFactory = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets whether to try automatically query for the current schema on application start /// and enable , and /// options depending on the current schema version. When storage /// is inaccessible on startup, default values will be used for those options. /// public bool TryAutoDetectSchemaDependentOptions { get; set; } /// /// Gets or sets a default queue provider that will be used when no special provider was /// registered for a particular queue. /// public IPersistentJobQueueProvider DefaultQueueProvider { get; set; } #if FEATURE_TRANSACTIONSCOPE /// /// Disables the use of the System.Transactions namespace and class /// and switches to regular explicit transactions usage in .NET Framework version of /// (as in .NET Core's implementation). Potentially fixes problems with abandoned locks and exhausted /// connection pool. /// /// /// This option only works with the default /// , throwing an exception when external queue providers are used /// (such as MSMQ-based or custom ones). /// public bool DisableTransactionScope { get; set; } #endif } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerTimeoutJob.cs ================================================ // This file is part of Hangfire. Copyright © 2017 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Threading; using Dapper; using Hangfire.Annotations; using Hangfire.Logging; using Hangfire.Storage; namespace Hangfire.SqlServer { internal sealed class SqlServerTimeoutJob : IFetchedJob { private readonly ILog _logger = LogProvider.GetLogger(typeof(SqlServerTimeoutJob)); private readonly object _syncRoot = new object(); private readonly SqlServerStorage _storage; private bool _disposed; private bool _removedFromQueue; private bool _requeued; private SqlServerWriteOnlyTransaction _transaction; private long _lastHeartbeat; private TimeSpan _interval; public SqlServerTimeoutJob( [NotNull] SqlServerStorage storage, long id, [NotNull] string jobId, [NotNull] string queue, [NotNull] DateTime? fetchedAt) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (jobId == null) throw new ArgumentNullException(nameof(jobId)); if (queue == null) throw new ArgumentNullException(nameof(queue)); if (fetchedAt == null) throw new ArgumentNullException(nameof(fetchedAt)); _storage = storage; Id = id; JobId = jobId; Queue = queue; FetchedAt = fetchedAt.Value; if (storage.SlidingInvisibilityTimeout.HasValue) { _lastHeartbeat = TimestampHelper.GetTimestamp(); _interval = TimeSpan.FromSeconds(storage.SlidingInvisibilityTimeout.Value.TotalSeconds / 5); storage.HeartbeatProcess.Track(this); } } public long Id { get; } public string JobId { get; } public string Queue { get; } internal DateTime? FetchedAt { get; private set; } public void RemoveFromQueue() { lock (_syncRoot) { if (_transaction != null && _transaction.Committed) return; if (!FetchedAt.HasValue) return; _storage.UseConnection( null, static (storage, connection, ctx) => connection.Execute( storage.GetQueryFromTemplate(static schemaName => $@"delete JQ from [{schemaName}].JobQueue JQ with (forceseek, rowlock) where Queue = @queue and Id = @id and FetchedAt = @fetchedAt"), new { queue = ctx.Queue, id = ctx.Id, fetchedAt = ctx.FetchedAt }, commandTimeout: storage.CommandTimeout), this); _removedFromQueue = true; } } public void Requeue() { lock (_syncRoot) { if (_transaction != null && _transaction.Committed) return; if (!FetchedAt.HasValue) return; _storage.UseConnection( null, static (storage, connection, ctx) => connection.Execute( storage.GetQueryFromTemplate(static schemaName => $@"update JQ set FetchedAt = null from [{schemaName}].JobQueue JQ with (forceseek, rowlock) where Queue = @queue and Id = @id and FetchedAt = @fetchedAt"), new { queue = ctx.Queue, id = ctx.Id, fetchedAt = ctx.FetchedAt }, commandTimeout: storage.CommandTimeout), this); FetchedAt = null; _requeued = true; } } public void Dispose() { if (_disposed) return; _disposed = true; DisposeTimer(); lock (_syncRoot) { if (!_removedFromQueue && !_requeued && (_transaction == null || !_transaction.Committed)) { Requeue(); } } } internal void SetTransaction(SqlServerWriteOnlyTransaction transaction) { lock (_syncRoot) { _transaction = transaction; } } internal void DisposeTimer() { _storage.HeartbeatProcess.Untrack(this); } internal void ExecuteKeepAliveQueryIfRequired() { var now = TimestampHelper.GetTimestamp(); if (TimestampHelper.Elapsed(now, Interlocked.Read(ref _lastHeartbeat)) >= _interval) { lock (_syncRoot) { if (!FetchedAt.HasValue) return; if (_requeued || _removedFromQueue) return; if (_transaction != null && _transaction.Committed) return; try { FetchedAt = _storage.UseConnection( null, static (storage, connection, ctx) => connection.ExecuteScalar( storage.GetQueryFromTemplate(static schemaName => $@"update JQ set FetchedAt = getutcdate() output INSERTED.FetchedAt from [{schemaName}].JobQueue JQ with (forceseek, rowlock) where Queue = @queue and Id = @id and FetchedAt = @fetchedAt"), new { queue = ctx.Queue, id = ctx.Id, fetchedAt = ctx.FetchedAt }, commandTimeout: storage.CommandTimeout), this); if (!FetchedAt.HasValue) { _logger.Warn( $"Background job identifier '{JobId}' was fetched by another worker, will not execute keep alive."); } _logger.Trace($"Keep-alive query for message {Id} sent"); Interlocked.Exchange(ref _lastHeartbeat, now); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { _logger.DebugException($"Unable to execute keep-alive query for message {Id}", ex); } } } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerTransactionJob.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Data; using System.Threading; using Dapper; using Hangfire.Annotations; using Hangfire.Storage; namespace Hangfire.SqlServer { internal sealed class SqlServerTransactionJob : IFetchedJob { // Connections to SQL Azure Database that are idle for 30 minutes // or longer will be terminated. And since we are using separate // connection for a holding a transaction during the background // job processing, we'd like to prevent Resource Governor from // terminating it. private static readonly TimeSpan KeepAliveInterval = TimeSpan.FromMinutes(1); private readonly SqlServerStorage _storage; private IDbConnection _connection; private readonly IDbTransaction _transaction; private readonly Timer _timer; private readonly object _lockObject = new object(); public SqlServerTransactionJob( [NotNull] SqlServerStorage storage, [NotNull] IDbConnection connection, [NotNull] IDbTransaction transaction, string jobId, string queue) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (connection == null) throw new ArgumentNullException(nameof(connection)); if (transaction == null) throw new ArgumentNullException(nameof(transaction)); if (jobId == null) throw new ArgumentNullException(nameof(jobId)); if (queue == null) throw new ArgumentNullException(nameof(queue)); _storage = storage; _connection = connection; _transaction = transaction; JobId = jobId; Queue = queue; if (!_storage.IsExistingConnection(_connection)) { _timer = new Timer(ExecuteKeepAliveQuery, null, KeepAliveInterval, KeepAliveInterval); } } public string JobId { get; } public string Queue { get; } public void RemoveFromQueue() { lock (_lockObject) { _transaction.Commit(); } } public void Requeue() { lock (_lockObject) { _transaction.Rollback(); } } public void Dispose() { // Timer callback may be invoked after the Dispose method call, // so we are using lock to avoid unsynchronized calls. lock (_lockObject) { _timer?.Dispose(); _transaction.Dispose(); _storage.ReleaseConnection(_connection); _connection = null; } } private void ExecuteKeepAliveQuery(object obj) { lock (_lockObject) { try { _connection?.Execute("SELECT 1;", transaction: _transaction); } catch (Exception ex) when (ex.IsCatchableExceptionType()) { // Connection was closed. So we can't continue to send // keep-alive queries. Unlike for distributed locks, // there is no any caveats of having this issue for // queues, because Hangfire guarantees only the "at least // once" processing. } } } } } ================================================ FILE: src/Hangfire.SqlServer/SqlServerWriteOnlyTransaction.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; // ReSharper disable RedundantAnonymousTypePropertyName namespace Hangfire.SqlServer { internal sealed class SqlServerWriteOnlyTransaction : JobStorageTransaction { private readonly Queue> _queueCommandQueue = new(); private readonly HashSet _queuesToSignal = new(); private readonly SqlServerStorage _storage; private readonly SqlServerConnection _connection; private readonly SortedDictionary>> _jobCommands = new(); private readonly SortedDictionary>> _counterCommands = new(); private readonly SortedDictionary>> _hashCommands = new(); private readonly SortedDictionary>> _listCommands = new(); private readonly SortedDictionary>> _setCommands = new(); private readonly SortedDictionary>> _queueCommands = new(); private readonly List> _lockCommands = new(); private readonly List _acquiredLocks = new(); private readonly SortedSet _lockedResources = new(); private bool _committed; public SqlServerWriteOnlyTransaction([NotNull] SqlServerConnection connection) { if (connection == null) throw new ArgumentNullException(nameof(connection)); _connection = connection; _storage = connection.Storage; } public bool Committed => _committed; public override void Commit() { try { _storage.UseTransaction(_connection.DedicatedConnection, static (storage, connection, transaction, ctx) => { using (var commandBatch = new SqlCommandBatch(connection, transaction, preferBatching: storage.CommandBatchMaxTimeout.HasValue)) { commandBatch.Append(connection.Create("set xact_abort on;set nocount on;")); foreach (var lockedResource in ctx._lockedResources) { commandBatch.Append(connection .Create("exec sp_getapplock @Resource=@resource, @LockMode=N'Exclusive'") .AddParameter("@resource", lockedResource, DbType.String, size: 255)); } AppendBatch(ctx._jobCommands, commandBatch); AppendBatch(ctx._counterCommands, commandBatch); AppendBatch(ctx._hashCommands, commandBatch); AppendBatch(ctx._listCommands, commandBatch); AppendBatch(ctx._setCommands, commandBatch); AppendBatch(ctx._queueCommands, commandBatch); foreach (var command in ctx._lockCommands) { commandBatch.Append(command.Item1); } commandBatch.CommandTimeout = storage.CommandTimeout; commandBatch.CommandBatchMaxTimeout = storage.CommandBatchMaxTimeout; commandBatch.ExecuteNonQuery(); foreach (var acquiredLock in ctx._acquiredLocks) { acquiredLock.TryReportReleased(); } foreach (var lockCommand in ctx._lockCommands) { var releaseResult = lockCommand.Item2.GetParameterValue(); if (releaseResult.HasValue && releaseResult.Value < 0) { throw new SqlServerDistributedLockException($"Could not release a lock on the resource '{lockCommand.Item3}': Server returned the '{releaseResult}' error."); } } foreach (var queueCommand in ctx._queueCommandQueue) { queueCommand(connection, transaction); } } }, this); _committed = true; } finally { foreach (var acquiredLock in _acquiredLocks) { acquiredLock.Dispose(); } } TrySignalListeningWorkers(); } public override void Dispose() { foreach (var acquiredLock in _acquiredLocks) { acquiredLock.Dispose(); } base.Dispose(); } public override void AcquireDistributedLock(string resource, TimeSpan timeout) { if (String.IsNullOrWhiteSpace(resource)) throw new ArgumentNullException(nameof(resource)); var disposableLock = _connection.AcquireLock($"{_storage.SchemaName}:{resource}", timeout); if (disposableLock.OwnLock) { var command = SqlServerDistributedLock.CreateReleaseCommand( _connection.DedicatedConnection, disposableLock.Resource, out var resultParameter); _lockCommands.Add(Tuple.Create(command, resultParameter, disposableLock.Resource)); } _acquiredLocks.Add(disposableLock); } public override void ExpireJob(string jobId, TimeSpan expireIn) { if (jobId == null) throw new ArgumentNullException(nameof(jobId)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"update J set ExpireAt = @expireAt from [{schemaName}].Job J with (forceseek) where Id = @id;"); var longId = long.Parse(jobId, CultureInfo.InvariantCulture); AddCommand(_jobCommands, longId, batch => batch.Create(query) .AddParameter("@expireAt", DateTime.UtcNow.Add(expireIn), DbType.DateTime) .AddParameter("@id", longId, DbType.Int64)); } public override void PersistJob(string jobId) { if (jobId == null) throw new ArgumentNullException(nameof(jobId)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"update J set ExpireAt = NULL from [{schemaName}].Job J with (forceseek) where Id = @id;"); var longId = long.Parse(jobId, CultureInfo.InvariantCulture); AddCommand(_jobCommands, longId, batch => batch.Create(query) .AddParameter("@id", longId, DbType.Int64)); } public override void SetJobState(string jobId, IState state) { if (jobId == null) throw new ArgumentNullException(nameof(jobId)); if (state == null) throw new ArgumentNullException(nameof(state)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].State (JobId, Name, Reason, CreatedAt, Data) values (@jobId, @name, @reason, @createdAt, @data); update [{schemaName}].Job set StateId = SCOPE_IDENTITY(), StateName = @name where Id = @jobId;"); var longId = long.Parse(jobId, CultureInfo.InvariantCulture); AddCommand(_jobCommands, longId, batch => batch.Create(query) .AddParameter("@jobId", longId, DbType.Int64) .AddParameter("@name", state.Name, DbType.String, size: 20) .AddParameter("@reason", (object)state.Reason?.Substring(0, Math.Min(99, state.Reason.Length)) ?? DBNull.Value, DbType.String, size: 100) .AddParameter("@createdAt", DateTime.UtcNow, DbType.DateTime) .AddParameter("@data", (object)SerializationHelper.Serialize(state.SerializeData()) ?? DBNull.Value, DbType.String, size: -1)); } public override void AddJobState(string jobId, IState state) { if (jobId == null) throw new ArgumentNullException(nameof(jobId)); if (state == null) throw new ArgumentNullException(nameof(state)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].State (JobId, Name, Reason, CreatedAt, Data) values (@jobId, @name, @reason, @createdAt, @data)"); var longId = long.Parse(jobId, CultureInfo.InvariantCulture); AddCommand(_jobCommands, longId, batch => batch.Create(query) .AddParameter("@jobId", longId, DbType.Int64) .AddParameter("@name", state.Name, DbType.String, size: 20) .AddParameter("@reason", (object)state.Reason?.Substring(0, Math.Min(99, state.Reason.Length)) ?? DBNull.Value, DbType.String, size: 100) .AddParameter("@createdAt", DateTime.UtcNow, DbType.DateTime) .AddParameter("@data", (object)SerializationHelper.Serialize(state.SerializeData()) ?? DBNull.Value, DbType.String, size: -1)); } public override void AddToQueue(string queue, string jobId) { if (queue == null) throw new ArgumentNullException(nameof(queue)); if (jobId == null) throw new ArgumentNullException(nameof(jobId)); var provider = _storage.QueueProviders.GetProvider(queue); var persistentQueue = provider.GetJobQueue(); if (persistentQueue.GetType() == typeof(SqlServerJobQueue)) { var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].JobQueue (JobId, Queue) values (@jobId, @queue)"); AddCommand(_queueCommands, queue, batch => batch.Create(query) .AddParameter("@jobId", long.Parse(jobId, CultureInfo.InvariantCulture), DbType.Int64) .AddParameter("@queue", queue, DbType.String)); _queuesToSignal.Add(queue); } else { #if FEATURE_TRANSACTIONSCOPE if (_storage.Options.DisableTransactionScope) { throw new NotSupportedException($"`{nameof(SqlServerStorageOptions.DisableTransactionScope)}` option does not support external queue providers"); } #endif _queueCommandQueue.Enqueue((connection, transaction) => persistentQueue.Enqueue( connection, #if !FEATURE_TRANSACTIONSCOPE transaction, #endif queue, jobId)); } } public override void IncrementCounter(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].Counter ([Key], [Value]) values (@key, @value)"); AddCommand(_counterCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value: +1, DbType.Int32)); } public override void IncrementCounter(string key, TimeSpan expireIn) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].Counter ([Key], [Value], [ExpireAt]) values (@key, @value, @expireAt)"); AddCommand(_counterCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value: +1, DbType.Int32) .AddParameter("@expireAt", DateTime.UtcNow.Add(expireIn), DbType.DateTime)); } public override void DecrementCounter(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].Counter ([Key], [Value]) values (@key, @value)"); AddCommand(_counterCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value: -1, DbType.Int32)); } public override void DecrementCounter(string key, TimeSpan expireIn) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].Counter ([Key], [Value], [ExpireAt]) values (@key, @value, @expireAt)"); AddCommand(_counterCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value: -1, DbType.Int32) .AddParameter("@expireAt", DateTime.UtcNow.Add(expireIn), DbType.DateTime)); } public override void AddToSet(string key, string value) { AddToSet(key, value, 0.0); } public override void AddToSet(string key, string value, double score) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); var query = _storage.GetQueryFromTemplate(_storage.Options.UseIgnoreDupKeyOption ? static schemaName => $@"insert into [{schemaName}].[Set] ([Key], Value, Score) values (@key, @value, @score); if @@ROWCOUNT = 0 update [{schemaName}].[Set] set Score = @score where [Key] = @key and Value = @value;" : static schemaName => $@";merge [{schemaName}].[Set] with (xlock) as Target using (VALUES (@key, @value, @score)) as Source ([Key], Value, Score) on Target.[Key] = Source.[Key] and Target.Value = Source.Value when matched then update set Score = Source.Score when not matched then insert ([Key], Value, Score) values (Source.[Key], Source.Value, Source.Score);"); AcquireSetLock(key); AddCommand(_setCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value, DbType.String, 256) .AddParameter("@score", score, DbType.Double, size: 53)); } public override void RemoveFromSet(string key, string value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"delete S from [{schemaName}].[Set] S with (forceseek) where [Key] = @key and Value = @value"); AcquireSetLock(key); AddCommand(_setCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value, DbType.String, size: 256)); } public override void InsertToList(string key, string value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); var query = _storage.GetQueryFromTemplate(static schemaName => $@" select [Key] from [{schemaName}].List with (xlock, forceseek) where [Key] = @key; insert into [{schemaName}].List ([Key], Value) values (@key, @value);"); AcquireListLock(key); AddCommand(_listCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value, DbType.String, size: -1)); } public override void RemoveFromList(string key, string value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"delete L from [{schemaName}].List L with (forceseek) where [Key] = @key and Value = @value"); AcquireListLock(key); AddCommand(_listCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", value, DbType.String, size: -1)); } public override void TrimList(string key, int keepStartingFrom, int keepEndingAt) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@";with cte as ( select row_number() over (order by Id desc) as row_num from [{schemaName}].List with (xlock, forceseek) where [Key] = @key) delete from cte where row_num not between @start and @end"); AcquireListLock(key); AddCommand(_listCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@start", keepStartingFrom + 1, DbType.Int32) .AddParameter("@end", keepEndingAt + 1, DbType.Int32)); } public override void SetRangeInHash(string key, IEnumerable> keyValuePairs) { if (key == null) throw new ArgumentNullException(nameof(key)); if (keyValuePairs == null) throw new ArgumentNullException(nameof(keyValuePairs)); var query = _storage.GetQueryFromTemplate(_storage.Options.UseIgnoreDupKeyOption ? static schemaName => $@"insert into [{schemaName}].Hash ([Key], Field, Value) values (@key, @field, @value); if @@ROWCOUNT = 0 update [{schemaName}].Hash set Value = @value where [Key] = @key and Field = @field;" : static schemaName => $@";merge [{schemaName}].Hash with (xlock) as Target using (VALUES (@key, @field, @value)) as Source ([Key], Field, Value) on Target.[Key] = Source.[Key] and Target.Field = Source.Field when matched then update set Value = Source.Value when not matched then insert ([Key], Field, Value) values (Source.[Key], Source.Field, Source.Value);"); AcquireHashLock(key); foreach (var pair in keyValuePairs) { AddCommand(_hashCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@field", pair.Key, DbType.String, size: 100) .AddParameter("@value", (object)pair.Value ?? DBNull.Value, DbType.String, size: -1)); } } public override void RemoveHash(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"delete H from [{schemaName}].Hash H with (forceseek) where [Key] = @key"); AcquireHashLock(key); AddCommand(_hashCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String)); } public override void AddRangeToSet(string key, IList items) { if (key == null) throw new ArgumentNullException(nameof(key)); if (items == null) throw new ArgumentNullException(nameof(items)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"insert into [{schemaName}].[Set] ([Key], Value, Score) values (@key, @value, 0.0)"); AcquireSetLock(key); foreach (var item in items) { AddCommand(_setCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@value", item, DbType.String, size: 256)); } } public override void RemoveSet(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@"delete S from [{schemaName}].[Set] S with (forceseek) where [Key] = @key"); AcquireSetLock(key); AddCommand(_setCommands, key, batch => batch.Create(query).AddParameter("@key", key, DbType.String)); } public override void ExpireHash(string key, TimeSpan expireIn) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@" update [{schemaName}].[Hash] set ExpireAt = @expireAt where [Key] = @key"); AcquireHashLock(key); AddCommand(_hashCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@expireAt", DateTime.UtcNow.Add(expireIn), DbType.DateTime)); } public override void ExpireSet(string key, TimeSpan expireIn) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@" update [{schemaName}].[Set] set ExpireAt = @expireAt where [Key] = @key"); AcquireSetLock(key); AddCommand(_setCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@expireAt", DateTime.UtcNow.Add(expireIn), DbType.DateTime)); } public override void ExpireList(string key, TimeSpan expireIn) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@" update [{schemaName}].[List] set ExpireAt = @expireAt where [Key] = @key"); AcquireListLock(key); AddCommand(_listCommands, key, batch => batch.Create(query) .AddParameter("@key", key, DbType.String) .AddParameter("@expireAt", DateTime.UtcNow.Add(expireIn), DbType.DateTime)); } public override void PersistHash(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@" update [{schemaName}].Hash set ExpireAt = null where [Key] = @key"); AcquireHashLock(key); AddCommand(_hashCommands, key, batch => batch.Create(query).AddParameter("@key", key, DbType.String)); } public override void PersistSet(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@" update [{schemaName}].[Set] set ExpireAt = null where [Key] = @key"); AcquireSetLock(key); AddCommand(_setCommands, key, batch => batch.Create(query).AddParameter("@key", key, DbType.String)); } public override void PersistList(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); var query = _storage.GetQueryFromTemplate(static schemaName => $@" update [{schemaName}].[List] set ExpireAt = null where [Key] = @key"); AcquireListLock(key); AddCommand(_listCommands, key, batch => batch.Create(query).AddParameter("@key", key, DbType.String)); } public override void RemoveFromQueue(IFetchedJob fetchedJob) { if (fetchedJob == null) throw new ArgumentNullException(nameof(fetchedJob)); if (fetchedJob is SqlServerTimeoutJob timeoutJob) { var query = _storage.GetQueryFromTemplate(static schemaName => $@"delete JQ from [{schemaName}].JobQueue JQ with (forceseek, rowlock) where Queue = @queue and Id = @id and FetchedAt = @fetchedAt"); AddCommand(_queueCommands, timeoutJob.Queue, batch => batch.Create(query) .AddParameter("@queue", timeoutJob.Queue, DbType.String) .AddParameter("@id", timeoutJob.Id, DbType.Int64) .AddParameter("@fetchedAt", timeoutJob.FetchedAt, DbType.DateTime)); timeoutJob.SetTransaction(this); } else { throw new NotSupportedException( "Only '" + nameof(SqlServerTimeoutJob) + "' type supports transactional acknowledge, '" + fetchedJob.GetType().Name + "' given."); } } private static void AppendBatch( SortedDictionary>> collection, SqlCommandBatch batch) { foreach (var pair in collection) { foreach (var command in pair.Value) { var dbCommand = command(batch.Connection); batch.Append(dbCommand); } } } private static void AddCommand( SortedDictionary>> collection, TKey key, Func command) { if (!collection.TryGetValue(key, out var commands)) { commands = new List>(); collection.Add(key, commands); } commands.Add(command); } private void AcquireListLock(string key) { AcquireLock(_storage.Options.DisableGlobalLocks ? $"List:{key}" : "List"); } private void AcquireSetLock(string key) { AcquireLock(_storage.Options.DisableGlobalLocks ? $"Set:{key}" : "Set"); } private void AcquireHashLock(string key) { AcquireLock(_storage.Options.DisableGlobalLocks ? $"Hash:{key}" : "Hash"); } private void AcquireLock(string resource) { if (!_storage.Options.DisableGlobalLocks || _storage.Options.UseFineGrainedLocks) { _lockedResources.Add($"{_storage.SchemaName}:{resource}:Lock"); } } private void TrySignalListeningWorkers() { foreach (var queue in _queuesToSignal) { if (SqlServerJobQueue.NewItemInQueueEvents.TryGetValue(Tuple.Create(_storage, queue), out var signal)) { signal.Set(); } } } } } ================================================ FILE: src/Hangfire.SqlServer/TimestampHelper.cs ================================================ // This file is part of Hangfire. Copyright © 2021 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; namespace Hangfire.SqlServer { internal static class TimestampHelper { public static long GetTimestamp() { #if NETCOREAPP3_0 return Environment.TickCount64; #else return Environment.TickCount; #endif } public static TimeSpan Elapsed(long timestamp) { var now = GetTimestamp(); return Elapsed(now, timestamp); } public static TimeSpan Elapsed(long now, long timestamp) { #if NETCOREAPP3_0 return TimeSpan.FromMilliseconds(now - timestamp); #else return TimeSpan.FromMilliseconds(unchecked((int)now - (int)timestamp)); #endif } } } ================================================ FILE: src/Hangfire.SqlServer/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.1": { "Dapper": { "type": "Direct", "requested": "[1.60.6, )", "resolved": "1.60.6", "contentHash": "mmnJNhKMeF2KhvVXDoVQlFxre8aJAo71YBJrKqFlvuqzYC2QiXUq94/GCDBJzU7paq4GqpkV2glw3308TcGibw==" }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net451": "1.0.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.NETFramework.ReferenceAssemblies.net451": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "vVPinxdLrwoX81ApbNIHDBI6qymQEy8eSOxDNBgKJtc2+cifnF0oT1U2d3EFx+V5O68yaqna2myZJNsgKCpVkA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } } }, ".NETStandard,Version=v1.3": { "Dapper": { "type": "Direct", "requested": "[1.60.6, )", "resolved": "1.60.6", "contentHash": "mmnJNhKMeF2KhvVXDoVQlFxre8aJAo71YBJrKqFlvuqzYC2QiXUq94/GCDBJzU7paq4GqpkV2glw3308TcGibw==", "dependencies": { "NETStandard.Library": "1.6.1", "System.Collections.Concurrent": "4.3.0", "System.Collections.NonGeneric": "4.3.0", "System.Data.SqlClient": "4.4.0", "System.Dynamic.Runtime": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.Lightweight": "4.3.0", "System.Reflection.TypeExtensions": "4.4.0", "System.Runtime.InteropServices": "4.3.0", "System.Xml.XmlDocument": "4.3.0" } }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "NETStandard.Library": { "type": "Direct", "requested": "[1.6.1, )", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "17h8b5mXa87XYKrrVqdgZ38JefSUqLChUQpXgSnpzsM0nDOhE40FTeNWOJ/YmySGV6tG6T8+hjz6vxbknHJr6A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Threading": "4.0.11" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "9.0.1", "contentHash": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", "dependencies": { "Microsoft.CSharp": "4.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Serialization.Primitives": "4.1.1", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11" } }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==", "dependencies": { "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" }, "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" }, "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Collections.NonGeneric": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Data.Common": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "lm6E3T5u7BOuEH0u18JpbJHxBfOJPuCyl4Kg1RH10ktYLp5uEEE1xKrHW56/We4SnZpGAuCc9N0MJpSDhTHZGQ==", "dependencies": { "System.Collections": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Data.SqlClient": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "fxb9ghn1k1Ua7FFdlvtiBOD4/PsQvD/fk2KnhS+FK7VC6OggEx6P+lP1P0+KMb5V2dqS1+FbR7HCenoqzJMNIA==", "dependencies": { "NETStandard.Library": "1.6.1", "System.Data.Common": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.4.0", "System.IO.Pipes": "4.3.0", "System.Net.NameResolution": "4.3.0", "System.Net.Security": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Security.Principal": "4.3.0", "System.Security.Principal.Windows": "4.4.0", "System.Text.Encoding.CodePages": "4.4.0", "System.Threading.Thread": "4.3.0", "System.Threading.ThreadPool": "4.3.0", "runtime.native.System.Data.SqlClient.sni": "4.4.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "2SMt95w+TKf2fth4mSlqn1AVNSmFDQkdTVmUe6D/oP1atzVU0vxTb+iDP+IHNQB1qSbYkWNoPN55SaMsGUe68A==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.IO.Pipes": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "wpGJuACA6r8+KRckXoI6ghGTwgPRiICI6T7kgHI/m7S5eMqV/8jH37fzAUhTwIe9RwlH/j1sWwm2Q2zyXwZGHw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Principal": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Overlapped": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Net.NameResolution": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "AFYl08R7MrsrEjqpQWTZWBadqXyTzNDaWpMqyxhb0d6sGhV6xMDKueuBXlLL30gz+DIRY6MpdgnHWlCh5wmq9w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Principal.Windows": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Security": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "IgJKNfALqw7JRWp5LMQ5SWHNKvXVz094U6wNE3c1i8bOkMQvgBL+MMQuNt3xl9Qg9iWpj3lFxPZEY6XHmROjMQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Claims": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Security.Principal": "4.3.0", "System.Security.Principal.Windows": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.ThreadPool": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", "dependencies": { "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "dkmh/ySlwnXJp/1qYP9uyKkCK1CXR/REFzl7abHcArxBcV91mY2CgrrzSRA5Z/X4MevJWwXsklGRdR3A7K9zbg==", "dependencies": { "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Runtime.Serialization.Primitives": { "type": "Transitive", "resolved": "4.1.1", "contentHash": "HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==", "dependencies": { "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Security.Claims": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "P/+BR/2lnc4PNDHt/TPBAWHVMLMRHsyYZbU1NphW4HIWzCggz8mJbTQQ3MKljFE7LS3WagmVFuBgoLcFzYXlkA==", "dependencies": { "System.Collections": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Security.Principal": "4.3.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0" } }, "System.Security.Principal": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I1tkfQlAoMM2URscUtpcRo/hX0jinXx6a/KUtEQoz3owaYwl3qwsO8cbzYVVnjxrzxjHo3nJC+62uolgeGIS9A==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Security.Principal.Windows": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "pP+AOzt1o3jESOuLmf52YQTF7H3Ng9hTnrOESQiqsnl2IbBh1HInsAMHYtoh75iUYV0OIkHmjvveraYB6zM97w==", "dependencies": { "Microsoft.Win32.Primitives": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Claims": "4.3.0", "System.Security.Principal": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Overlapped": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m3HQ2dPiX/DSTpf+yJt8B0c+SRvzfqAJKx+QDWi+VLhz8svLT23MVjEOHPF/KiSLeArKU/iHescrbLd3yVgyNg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading.ThreadPool": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", "dependencies": { "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "NETStandard.Library": "[1.6.1, )", "Newtonsoft.Json": "[9.0.1, )", "System.Threading.Thread": "[4.0.0, )", "System.Threading.ThreadPool": "[4.0.10, )" } } }, ".NETStandard,Version=v2.0": { "Dapper": { "type": "Direct", "requested": "[2.1.28, )", "resolved": "2.1.28", "contentHash": "ha49pzOEDmCPkMxwfPSR/wxa/6RD3r42TESIgpzpi7FXq/gNVUuJVEO+wtUzntNRhtmq3BKCl0s0aAlSZLkBUA==", "dependencies": { "System.Reflection.Emit.Lightweight": "4.7.0" } }, "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "11.0.1", "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "AucBYo3DSI0IDxdUjKksBcQJXPHyoPyrCXYURW1WDsLI4M65Ar/goSHjdnHOAY9MiYDNKqDlIgaYm+zL2hA1KA==" }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA==", "dependencies": { "System.Reflection.Emit.ILGeneration": "4.7.0" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } } } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/Hangfire.SqlServer.Msmq.csproj ================================================  net451 true Hangfire.SqlServer.Msmq ================================================ FILE: src/Hangfire.SqlServer.Msmq/IMsmqTransaction.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Messaging; namespace Hangfire.SqlServer.Msmq { internal interface IMsmqTransaction : IDisposable { Message Receive(MessageQueue queue, TimeSpan timeout); void Commit(); void Abort(); } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MessageQueueExtensions.cs ================================================ // The MIT License (MIT) // // Copyright (c) 2014 Philip Hoppe // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.ComponentModel; using System.Messaging; using System.Runtime.InteropServices; using System.Text.RegularExpressions; // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Local // ReSharper disable FieldCanBeMadeReadOnly.Local // ReSharper disable MemberCanBePrivate.Local // ReSharper disable RedundantCast // ReSharper disable CheckNamespace namespace MQTools { internal static class MessageQueueExtensions { #region P/Invoke stuff [DllImport("mqrt.dll")] private static extern int MQMgmtGetInfo( [MarshalAs(UnmanagedType.BStr)]string computerName, [MarshalAs(UnmanagedType.BStr)]string objectName, ref MQMGMTPROPS mgmtProps); private const byte VT_NULL = 1; private const byte VT_UI4 = 19; private const int PROPID_MGMT_QUEUE_MESSAGE_COUNT = 7; //size must be 16 [StructLayout(LayoutKind.Sequential)] private struct MQPROPVariant { public byte vt; //0 public byte spacer; //1 public short spacer2; //2 public int spacer3; //4 public uint ulVal; //8 public int spacer4; //12 } //size must be 16 in x86 and 28 in x64 [StructLayout(LayoutKind.Sequential)] private struct MQMGMTPROPS { public uint cProp; public IntPtr aPropID; public IntPtr aPropVar; public IntPtr status; } #endregion private const int MQ_ERROR = unchecked((int)0xC00E0001); // A non-specific Message Queuing error was generated. For example, information about a queue that is currently not the active queue was requested. private const int MQ_ERROR_ACCESS_DENIED = unchecked((int)0xC00E0025); // The access rights for retrieving information about the applicable msmq (MSMQ-Configuration) or queue object are not allowed for the calling process. private const int MQ_ERROR_ILLEGAL_FORMATNAME = unchecked((int)0xC00E001E); // The specified format name in pObjectName is illegal. private const int MQ_ERROR_ILLEGAL_PROPERTY_VT = unchecked((int)0xC00E0019); // An invalid type indicator was supplied for one of the properties specified in pMgmtProps. private const int MQ_ERROR_QUEUE_NOT_ACTIVE = unchecked((int)0xC00E0004); // The queue is not open or may not exist. private const int MQ_ERROR_SERVICE_NOT_AVAILABLE = unchecked((int)0xC00E000B); // The Message Queuing service is not available. // ReSharper disable once RedundantOverflowCheckingContext private const int MQ_INFORMATION_UNSUPPORTED_PROPERTY = unchecked((int)0x400E0004); // An unsupported property identifier was specified in pMgmtProps const string QueueRegex = @"^(?:(.*\:)|)((?[^\\]*)|\.)(?:\\(?.*)|)\\(?.*)$"; private static readonly Regex regex = new Regex(QueueRegex, RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(5)); public static long GetCount(this MessageQueue messageQueue) { var match = GetQueuePathMatch(messageQueue.Path); var computerName = match.Groups["computerName"].Value; var queueType = match.Groups["queueType"].Value; var queue = match.Groups["queue"].Value; if (computerName == ".") computerName = null; return GetQueueCount(computerName, queueType, queue); } internal static Match GetQueuePathMatch(string queuePath) { var matches = regex.Matches(queuePath); if (matches.Count != 1) { throw new InvalidOperationException($"Unable to parse queue path '{queuePath}'"); } return matches[0]; } private static long GetQueueCount(string computerName, string queueType, string queue) { if (string.IsNullOrEmpty(computerName)) computerName = null; string queuePath = $"queue=Direct=OS:{computerName ?? "."}"; if (!String.IsNullOrEmpty(queueType)) { queuePath += $"\\{queueType}"; } queuePath += $"\\{queue}"; return GetCount(computerName, queuePath); } private static long GetCount(string computerName, string queuePath) { var props = new MQMGMTPROPS { cProp = 1, aPropID = Marshal.AllocHGlobal(sizeof(int)), aPropVar = Marshal.AllocHGlobal(Marshal.SizeOf()), status = Marshal.AllocHGlobal(sizeof(int)) }; Marshal.WriteInt32(props.aPropID, PROPID_MGMT_QUEUE_MESSAGE_COUNT); Marshal.StructureToPtr(new MQPROPVariant { vt = VT_NULL }, props.aPropVar, false); Marshal.WriteInt32(props.status, 0); try { int result = MQMgmtGetInfo(computerName, queuePath, ref props); //Console.WriteLine("{0} {1} Result:{2:X}", computerName, queuePath, result); switch (result) { case 0: break; case MQ_ERROR_QUEUE_NOT_ACTIVE: return 0; default: throw new Win32Exception(result); } if (Marshal.ReadInt32(props.status) != 0) return -1; var variant = Marshal.PtrToStructure(props.aPropVar); if (variant.vt != VT_UI4) return -2; return variant.ulVal; } finally { Marshal.FreeHGlobal(props.aPropID); Marshal.FreeHGlobal(props.aPropVar); Marshal.FreeHGlobal(props.status); } } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqDtcTransaction.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Messaging; using System.Transactions; namespace Hangfire.SqlServer.Msmq { internal sealed class MsmqDtcTransaction : IMsmqTransaction { private readonly TransactionScope _scope; private TransactionScope _suppressedScope; public MsmqDtcTransaction() { _scope = new TransactionScope(TransactionScopeOption.Required, TimeSpan.Zero); } public void Dispose() { if (_suppressedScope != null) { _suppressedScope.Complete(); _suppressedScope.Dispose(); } _scope.Dispose(); } public Message Receive(MessageQueue queue, TimeSpan timeout) { var message = queue.Receive(timeout, MessageQueueTransactionType.Automatic); _suppressedScope = new TransactionScope(TransactionScopeOption.Suppress, TimeSpan.Zero); return message; } public void Commit() { _scope.Complete(); } public void Abort() { } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using Hangfire.SqlServer; using Hangfire.SqlServer.Msmq; using Hangfire.States; // ReSharper disable once CheckNamespace namespace Hangfire { public static class MsmqExtensions { public static IGlobalConfiguration UseMsmqQueues( this IGlobalConfiguration configuration, string pathPattern, params string[] queues) { return UseMsmqQueues(configuration, MsmqTransactionType.Internal, pathPattern, queues); } public static IGlobalConfiguration UseMsmqQueues( this IGlobalConfiguration configuration, MsmqTransactionType transactionType, string pathPattern, params string[] queues) { if (queues.Length == 0) { queues = new[] { EnqueuedState.DefaultQueue }; } var provider = new MsmqJobQueueProvider(pathPattern, queues, transactionType); configuration.Entry.QueueProviders.Add(provider, queues); return configuration; } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqFetchedJob.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.Storage; namespace Hangfire.SqlServer.Msmq { internal sealed class MsmqFetchedJob : IFetchedJob { private readonly IMsmqTransaction _transaction; public MsmqFetchedJob([NotNull] IMsmqTransaction transaction, [NotNull] string jobId) { if (transaction == null) throw new ArgumentNullException(nameof(transaction)); if (jobId == null) throw new ArgumentNullException(nameof(jobId)); _transaction = transaction; JobId = jobId; } public string JobId { get; } public void RemoveFromQueue() { _transaction.Commit(); } public void Requeue() { _transaction.Abort(); } public void Dispose() { _transaction.Dispose(); } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqInternalTransaction.cs ================================================ // This file is part of Hangfire. Copyright © 2015 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Messaging; namespace Hangfire.SqlServer.Msmq { internal sealed class MsmqInternalTransaction : IMsmqTransaction { private readonly MessageQueueTransaction _transaction; public MsmqInternalTransaction() { _transaction = new MessageQueueTransaction(); } public void Dispose() { _transaction.Dispose(); } public Message Receive(MessageQueue queue, TimeSpan timeout) { _transaction.Begin(); return queue.Receive(timeout, _transaction); } public void Commit() { _transaction.Commit(); } public void Abort() { _transaction.Abort(); } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqJobQueue.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Data; using System.Globalization; using System.Messaging; using System.Threading; using Hangfire.Storage; namespace Hangfire.SqlServer.Msmq { internal sealed class MsmqJobQueue : IPersistentJobQueue { private static readonly TimeSpan SyncReceiveTimeout = TimeSpan.FromSeconds(5); private readonly string _pathPattern; private readonly MsmqTransactionType _transactionType; public MsmqJobQueue(string pathPattern, MsmqTransactionType transactionType) { if (pathPattern == null) throw new ArgumentNullException(nameof(pathPattern)); _pathPattern = pathPattern; _transactionType = transactionType; } public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken) { string jobId = null; var queueIndex = 0; while (!cancellationToken.IsCancellationRequested) { var transaction = CreateTransaction(); try { using (var messageQueue = GetMessageQueue(queues[queueIndex])) { var message = queueIndex == queues.Length - 1 ? transaction.Receive(messageQueue, SyncReceiveTimeout) : transaction.Receive(messageQueue, new TimeSpan(1)); jobId = message.Label; return new MsmqFetchedJob(transaction, jobId); } } catch (MessageQueueException ex) when (ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout) { // Receive timeout occurred, we should just switch to the next queue } finally { if (jobId == null) { transaction.Dispose(); } } queueIndex = (queueIndex + 1) % queues.Length; } cancellationToken.ThrowIfCancellationRequested(); return null; } public void Enqueue(IDbConnection connection, string queue, string jobId) { using (var messageQueue = GetMessageQueue(queue)) using (var message = new Message { Label = jobId }) using (var transaction = new MessageQueueTransaction()) { transaction.Begin(); messageQueue.Send(message, transaction); transaction.Commit(); } } private IMsmqTransaction CreateTransaction() { switch (_transactionType) { case MsmqTransactionType.Internal: return new MsmqInternalTransaction(); case MsmqTransactionType.Dtc: return new MsmqDtcTransaction(); } throw new InvalidOperationException("Unknown MSMQ transaction type: " + _transactionType); } private MessageQueue GetMessageQueue(string queue) { return new MessageQueue(String.Format(CultureInfo.InvariantCulture, _pathPattern, queue)); } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqJobQueueMonitoringApi.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Messaging; using MQTools; namespace Hangfire.SqlServer.Msmq { internal sealed class MsmqJobQueueMonitoringApi : IPersistentJobQueueMonitoringApi { private readonly string _pathPattern; private readonly IEnumerable _queues; public MsmqJobQueueMonitoringApi(string pathPattern, IEnumerable queues) { if (pathPattern == null) throw new ArgumentNullException(nameof(pathPattern)); if (queues == null) throw new ArgumentNullException(nameof(queues)); _pathPattern = pathPattern; _queues = queues; } public IEnumerable GetQueues() { return _queues; } public IEnumerable GetEnqueuedJobIds(string queue, int @from, int perPage) { var result = new List(); using (var messageQueue = new MessageQueue(String.Format(CultureInfo.InvariantCulture, _pathPattern, queue))) { var current = 0; var end = from + perPage; var enumerator = messageQueue.GetMessageEnumerator2(); while (enumerator.MoveNext()) { if (current >= from && current < end) { var message = enumerator.Current; if (message == null) continue; result.Add(long.Parse(message.Label, CultureInfo.InvariantCulture)); } if (current >= end) break; current++; } } return result; } public IEnumerable GetFetchedJobIds(string queue, int @from, int perPage) { return Enumerable.Empty(); } public EnqueuedAndFetchedCountDto GetEnqueuedAndFetchedCount(string queue) { using (var messageQueue = new MessageQueue(String.Format(CultureInfo.InvariantCulture, _pathPattern, queue))) { return new EnqueuedAndFetchedCountDto { EnqueuedCount = (int?)messageQueue.GetCount() }; } } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqJobQueueProvider.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System.Collections.Generic; namespace Hangfire.SqlServer.Msmq { internal sealed class MsmqJobQueueProvider : IPersistentJobQueueProvider { private readonly MsmqJobQueue _jobQueue; private readonly MsmqJobQueueMonitoringApi _monitoringApi; public MsmqJobQueueProvider(string pathPattern, IEnumerable queues, MsmqTransactionType transactionType) { _jobQueue = new MsmqJobQueue(pathPattern, transactionType); _monitoringApi = new MsmqJobQueueMonitoringApi(pathPattern, queues); } public IPersistentJobQueue GetJobQueue() { return _jobQueue; } public IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi() { return _monitoringApi; } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqSqlServerStorageExtensions.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . using System; using Hangfire.Annotations; using Hangfire.States; namespace Hangfire.SqlServer.Msmq { public static class MsmqSqlServerStorageExtensions { public static SqlServerStorage UseMsmqQueues( [NotNull] this SqlServerStorage storage, [NotNull] string pathPattern) { return UseMsmqQueues(storage, pathPattern, EnqueuedState.DefaultQueue); } public static SqlServerStorage UseMsmqQueues( [NotNull] this SqlServerStorage storage, [NotNull] string pathPattern, params string[] queues) { return UseMsmqQueues(storage, MsmqTransactionType.Internal, pathPattern, queues); } public static SqlServerStorage UseMsmqQueues( [NotNull] this SqlServerStorage storage, MsmqTransactionType transactionType, [NotNull] string pathPattern, params string[] queues) { if (storage == null) throw new ArgumentNullException(nameof(storage)); if (queues.Length == 0) { queues = new[] { EnqueuedState.DefaultQueue }; } var provider = new MsmqJobQueueProvider(pathPattern, queues, transactionType); storage.QueueProviders.Add(provider, queues); return storage; } } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/MsmqTransactionType.cs ================================================ // This file is part of Hangfire. Copyright © 2013-2014 Hangfire OÜ. // // Hangfire is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 // of the License, or any later version. // // Hangfire is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with Hangfire. If not, see . namespace Hangfire.SqlServer.Msmq { public enum MsmqTransactionType { /// /// Internal (MSMQ) transaction will be used to fetch pending background /// jobs, does not support remote queues. /// Internal, /// /// External (DTC) transaction will be used to fetch pending background /// jobs. Supports remote queues, but requires running MSDTC Service. /// Dtc } } ================================================ FILE: src/Hangfire.SqlServer.Msmq/Properties/AssemblyInfo.cs ================================================ using System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Hangfire.SqlServer.MSMQ")] [assembly: AssemblyDescription("Hangfire MSMQ job queue for SQL Server storage implementation")] [assembly: Guid("03092b5c-0dfc-4c6c-8422-556bd1cb291e")] [assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("Hangfire.SqlServer.Msmq.Tests")] // Allow the generation of mocks for internal types [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] ================================================ FILE: src/Hangfire.SqlServer.Msmq/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.1": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[9.0.0, )", "resolved": "9.0.0", "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net451": "1.0.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", "resolved": "8.0.0", "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", "dependencies": { "Microsoft.Build.Tasks.Git": "8.0.0", "Microsoft.SourceLink.Common": "8.0.0" } }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Dapper": { "type": "Transitive", "resolved": "1.60.6", "contentHash": "mmnJNhKMeF2KhvVXDoVQlFxre8aJAo71YBJrKqFlvuqzYC2QiXUq94/GCDBJzU7paq4GqpkV2glw3308TcGibw==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.NETFramework.ReferenceAssemblies.net451": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "vVPinxdLrwoX81ApbNIHDBI6qymQEy8eSOxDNBgKJtc2+cifnF0oT1U2d3EFx+V5O68yaqna2myZJNsgKCpVkA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Microsoft.SourceLink.Common": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[1.60.6, )", "Hangfire.Core": "[1.0.0, )" } } } } } ================================================ FILE: src/SharedAssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.InteropServices; [assembly: AssemblyProduct("Hangfire")] [assembly: AssemblyCompany("Hangfire OÜ")] [assembly: AssemblyCopyright("Copyright © 2013-2026 Hangfire OÜ")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] // Please don't edit it manually, use the `build.bat version` command instead. [assembly: AssemblyVersion("1.8.23")] ================================================ FILE: tests/Directory.Build.props ================================================ false ================================================ FILE: tests/Hangfire.Core.Tests/BackgroundJobClientExtensionsFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Common; using Hangfire.States; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests { public class BackgroundJobClientExtensionsFacts { private const string JobId = "job-id"; private readonly Mock _client; private readonly Mock _state; public BackgroundJobClientExtensionsFacts() { _client = new Mock(); _state = new Mock(); } [Fact] public void StaticCreate_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Create( null, () => StaticMethod(), _state.Object)); Assert.Equal("client", exception.ParamName); } [Fact] public void StaticCreate_ShouldCreateAJobInTheGivenState() { _client.Object.Create(() => StaticMethod(), _state.Object); _client.Verify(x => x.Create(It.IsNotNull(), _state.Object)); } [Fact] public void InstanceCreate_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Create( null, x => x.InstanceMethod(), _state.Object)); Assert.Equal("client", exception.ParamName); } [Fact] public void InstanceCreate_ShouldCreateAJobInTheGivenState() { _client.Object.Create(x => x.InstanceMethod(), _state.Object); _client.Verify(x => x.Create(It.IsNotNull(), _state.Object)); } [Fact] public void StaticEnqueue_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Enqueue( null, () => StaticMethod())); Assert.Equal("client", exception.ParamName); } [Fact] public void StaticEnqueue_ShouldCreateAJobInTheEnqueueState() { _client.Object.Enqueue(() => StaticMethod()); _client.Verify(x => x.Create(It.IsNotNull(), It.IsAny())); } [Fact] public void InstanceEnqueue_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Enqueue( null, x => x.InstanceMethod())); Assert.Equal("client", exception.ParamName); } [Fact] public void InstanceEnqueue_ShouldCreateAJobInTheEnqueuedState() { _client.Object.Enqueue(x => x.InstanceMethod()); _client.Verify(x => x.Create(It.IsNotNull(), It.IsAny())); } [Fact] public void StaticSchedule_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Schedule( null, () => StaticMethod(), TimeSpan.FromDays(1))); Assert.Equal("client", exception.ParamName); } [Fact] public void StaticSchedule_ShouldCreateAJobInTheScheduledState() { _client.Object.Schedule(() => StaticMethod(), TimeSpan.FromDays(1)); _client.Verify(x => x.Create( It.IsNotNull(), It.Is(state => state.EnqueueAt > DateTime.UtcNow))); } [Fact] public void StaticSchedule_WithDateTimeOffset_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Schedule( null, () => StaticMethod(), DateTimeOffset.UtcNow)); Assert.Equal("client", exception.ParamName); } [Fact] public void StaticSchedule_WithDateTimeOffset_ShouldCreateAJob_InTheScheduledState() { var now = DateTimeOffset.Now; _client.Object.Schedule(() => StaticMethod(), now); _client.Verify(x => x.Create( It.IsNotNull(), It.Is(state => state.EnqueueAt == now.UtcDateTime))); } [Fact] public void InstanceSchedule_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Schedule( null, x => x.InstanceMethod(), TimeSpan.FromDays(1))); Assert.Equal("client", exception.ParamName); } [Fact] public void InstanceSchedule_ShouldCreateAJobInTheScheduledState() { _client.Object.Schedule( x => x.InstanceMethod(), TimeSpan.FromDays(1)); _client.Verify(x => x.Create( It.IsNotNull(), It.Is(state => state.EnqueueAt > DateTime.UtcNow))); } [Fact] public void InstanceSchedule_WithDateTimeOffset_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Schedule( null, x => x.InstanceMethod(), DateTimeOffset.UtcNow)); Assert.Equal("client", exception.ParamName); } [Fact] public void InstanceSchedule_WithDateTimeOffset_ShouldCreateAJobInTheScheduledState() { var now = DateTimeOffset.Now; _client.Object.Schedule( x => x.InstanceMethod(), now); _client.Verify(x => x.Create( It.IsNotNull(), It.Is(state => state.EnqueueAt == now.UtcDateTime))); } [Fact] public void ChangeState_WithoutFromState_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.ChangeState(null, "job-id", _state.Object)); Assert.Equal("client", exception.ParamName); } [Fact] public void ChangeState_WithoutFromState_CallsItsOverload() { _client.Object.ChangeState("job-id", _state.Object); _client.Verify(x => x.ChangeState("job-id", _state.Object, null)); } [Fact] public void Delete_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Delete(null, JobId)); Assert.Equal("client", exception.ParamName); } [Fact] public void Delete_ChangesTheStateOfAJob_ToDeleted() { _client.Object.Delete(JobId); _client.Verify(x => x.ChangeState( JobId, It.IsAny(), null)); } [Fact] public void Delete_WithFromState_ChangesTheStateOfAJob_ToDeletedWithFromStateValue() { _client.Object.Delete(JobId, FailedState.StateName); _client.Verify(x => x.ChangeState( JobId, It.IsAny(), FailedState.StateName)); } [Fact] public void Requeue_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Requeue(null, JobId, FailedState.StateName)); Assert.Equal("client", exception.ParamName); } [Fact] public void Requeue_ChangesTheStateOfAJob_ToEnqueued() { _client.Object.Requeue(JobId); _client.Verify(x => x.ChangeState(JobId, It.IsAny(), null)); } [Fact] public void Requeue_WithFromState_ChangesTheStateOfAJob_ToEnqueued_FromTheGivenState() { _client.Object.Requeue(JobId, FailedState.StateName); _client.Verify(x => x.ChangeState(JobId, It.IsAny(), FailedState.StateName)); } [Fact] public void Reschedule_ThrowsAnException_WhenClientIsNull() { var exception = Assert.Throws( () => BackgroundJobClientExtensions.Reschedule(null, JobId, TimeSpan.FromDays(1), FailedState.StateName)); Assert.Equal("client", exception.ParamName); } [Fact] public void Reschedule_ChangesTheStateOfAJob_ToScheduled() { _client.Object.Reschedule(JobId, TimeSpan.FromDays(1)); _client.Verify(x => x.ChangeState(JobId, It.Is(state => state.EnqueueAt > DateTime.UtcNow), null)); } [Fact] public void Reschedule_WithFromState_ChangesTheStateOfAJob_ToScheduled_FromTheGivenState() { _client.Object.Reschedule(JobId, TimeSpan.FromDays(1), FailedState.StateName); _client.Verify(x => x.ChangeState(JobId, It.Is(state => state.EnqueueAt > DateTime.UtcNow), FailedState.StateName)); } [Fact] public void Reschedule_WithDateTimeOffset_ChangesTheStateOfAJob_ToScheduled() { var now = DateTimeOffset.Now; _client.Object.Reschedule(JobId, now); _client.Verify(x => x.ChangeState(JobId, It.Is(state => state.EnqueueAt == now.UtcDateTime), null)); } [Fact] public void Reschedule_WithDateTimeOffset_WithFromState_ChangesTheStateOfAJob_ToScheduled_FromTheGivenState() { var now = DateTimeOffset.Now; _client.Object.Reschedule(JobId, now, FailedState.StateName); _client.Verify(x => x.ChangeState(JobId, It.Is(state => state.EnqueueAt == now.UtcDateTime), FailedState.StateName)); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void StaticMethod() { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public void InstanceMethod() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/BackgroundJobClientFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; #pragma warning disable 618 // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests { public class BackgroundJobClientFacts { private readonly Mock _storage; private readonly Mock _factory; private readonly Mock _state; private readonly Job _job; private readonly Mock _stateChanger; public BackgroundJobClientFacts() { var connection = new Mock(); _storage = new Mock(); _storage.Setup(x => x.GetConnection()).Returns(connection.Object); _stateChanger = new Mock(); _state = new Mock(); _state.Setup(x => x.Name).Returns("Mock"); _job = Job.FromExpression(() => Method()); _factory = new Mock(); _factory.Setup(x => x.Create(It.IsAny())) .Returns(new BackgroundJob("some-job", _job, DateTime.UtcNow)); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new BackgroundJobClient(null, _factory.Object, _stateChanger.Object)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenFactoryIsNull() { var exception = Assert.Throws( () => new BackgroundJobClient(_storage.Object, null, _stateChanger.Object)); Assert.Equal("factory", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenStateChangerIsNull() { var exception = Assert.Throws( () => new BackgroundJobClient(_storage.Object, _factory.Object, null)); Assert.Equal("stateChanger", exception.ParamName); } [Fact, GlobalLock(Reason = "Needs JobStorage.Current instance")] public void Ctor_UsesCurrent_JobStorageInstance_ByDefault() { JobStorage.Current = new Mock().Object; // ReSharper disable once ObjectCreationAsStatement // Does not throw new BackgroundJobClient(); } [Fact] public void CreateJob_ThrowsAnException_WhenJobIsNull() { var client = CreateClient(); var exception = Assert.Throws( () => client.Create(null, _state.Object)); Assert.Equal("job", exception.ParamName); } [Fact] public void CreateJob_ThrowsAnException_WhenStateIsNull() { var client = CreateClient(); var exception = Assert.Throws( () => client.Create(_job, null)); Assert.Equal("state", exception.ParamName); } [Fact] public void CreateJob_DelegatesBackgroundJobCreation_ToFactory() { var client = CreateClient(); client.Create(_job, _state.Object); _factory.Verify(x => x.Create(It.IsNotNull())); } [Fact] public void CreateJob_ReturnsJobIdentifier() { var client = CreateClient(); var id = client.Create(_job, _state.Object); Assert.Equal("some-job", id); } [Fact] public void CreateJob_WrapsOccurringExceptions_IntoItsOwnException() { var client = CreateClient(); _factory.Setup(x => x.Create(It.IsAny())) .Throws(); var exception = Assert.Throws( () => client.Create(_job, _state.Object)); Assert.NotNull(exception.InnerException); Assert.IsType(exception.InnerException); } [Fact] public void ChangeState_ThrowsAnException_WhenJobIdIsNull() { var client = CreateClient(); var exception = Assert.Throws( () => client.ChangeState(null, _state.Object, null)); Assert.Equal("jobId", exception.ParamName); } [Fact] public void ChangeState_ThrowsAnException_WhenStateIsNull() { var client = CreateClient(); var exception = Assert.Throws( () => client.ChangeState("jobId", null, null)); Assert.Equal("state", exception.ParamName); } [Fact] public void ChangeState_ChangesTheStateOfAJob_ToTheGivenOne() { var client = CreateClient(); client.ChangeState("job-id", _state.Object, null); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == "job-id" && ctx.NewState == _state.Object && ctx.ExpectedStates == null))); } [Fact] public void ChangeState_WithFromState_ChangesTheStateOfAJob_WithFromStateValue() { var client = CreateClient(); client.ChangeState("job-id", _state.Object, "State"); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == "job-id" && ctx.NewState == _state.Object && ctx.ExpectedStates.SequenceEqual(new[] { "State" })))); } [Fact] public void ChangeState_ReturnsTheResult_OfStateChangerInvocation() { _stateChanger.Setup(x => x.ChangeState(It.IsAny())) .Returns(_state.Object); var client = CreateClient(); var result = client.ChangeState("job-id", _state.Object, null); Assert.True(result); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Method() { } private BackgroundJobClient CreateClient() { return new BackgroundJobClient(_storage.Object, _factory.Object, _stateChanger.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/BackgroundJobFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Common; using Hangfire.States; using Moq; using Xunit; // ReSharper disable PossibleNullReferenceException namespace Hangfire.Core.Tests { public class BackgroundJobFacts { private readonly Mock _client; public BackgroundJobFacts() { _client = new Mock(); } [Fact, GlobalLock(Reason = "Access BackgroundJob.ClientFactory member")] public void Enqueue_CreatesAJobInEnqueuedState() { Initialize(); BackgroundJob.Enqueue(() => Method()); _client.Verify(x => x.Create(It.IsNotNull(), It.IsAny())); } [Fact, GlobalLock(Reason = "Access BackgroundJob.ClientFactory member")] public void EnqueueGeneric_CreatesAJobInEnqueuedState() { Initialize(); BackgroundJob.Enqueue(x => x.Method()); _client.Verify(x => x.Create(It.IsNotNull(), It.IsAny())); } [Fact, GlobalLock(Reason = "Access BackgroundJob.ClientFactory member")] public void Schedule_WithTimeSpan_CreatesAJobInScheduledState() { Initialize(); BackgroundJob.Schedule(() => Method(), TimeSpan.FromDays(1)); _client.Verify(x => x.Create( It.IsNotNull(), It.Is(state => state.EnqueueAt > DateTime.UtcNow))); } [Fact, GlobalLock(Reason = "Access BackgroundJob.ClientFactory member")] public void Schedule_WithDateTimeOffset_CreatesAJobInScheduledState() { Initialize(); BackgroundJob.Schedule(() => Method(), DateTimeOffset.Now); _client.Verify(x => x.Create( It.IsNotNull(), It.IsNotNull())); } [Fact, GlobalLock(Reason = "Access BackgroundJob.ClientFactory member")] public void ScheduleGeneric_WithTimeSpan_CreatesAJobInScheduledState() { Initialize(); BackgroundJob.Schedule(x => Method(), TimeSpan.FromDays(1)); _client.Verify(x => x.Create( It.IsNotNull(), It.Is(state => state.EnqueueAt > DateTime.UtcNow))); } [Fact, GlobalLock(Reason = "Access BackgroundJob.ClientFactory member")] public void ScheduleGeneric_WithDateTimeOffset_CreatesAJobInScheduledState() { Initialize(); BackgroundJob.Schedule(x => x.Method(), DateTimeOffset.Now); _client.Verify(x => x.Create( It.IsNotNull(), It.IsNotNull())); } [Fact, GlobalLock] public void Delete_ChangesStateOfAJobToDeleted() { Initialize(); BackgroundJob.Delete("job-id"); _client.Verify(x => x.ChangeState( "job-id", It.IsAny(), null)); } [Fact, GlobalLock] public void Delete_WithFromState_ChangesStateOfAJobToDeleted_WithFromState() { Initialize(); BackgroundJob.Delete("job-id", FailedState.StateName); _client.Verify(x => x.ChangeState( "job-id", It.IsAny(), FailedState.StateName)); } [Fact, GlobalLock] public void Requeue_ChangesStateOfAJobToEnqueued() { Initialize(); BackgroundJob.Requeue("job-id"); _client.Verify(x => x.ChangeState( "job-id", It.IsAny(), null)); } [Fact, GlobalLock] public void Requeue_WithFromState_ChangesStateOfAJobToEnqueued_WithFromState() { Initialize(); BackgroundJob.Requeue("job-id", FailedState.StateName); _client.Verify(x => x.ChangeState( "job-id", It.IsAny(), FailedState.StateName)); } [Fact, GlobalLock] public void Reschedule_WithTimeSpan_ChangesStateOfAJobToScheduled() { Initialize(); BackgroundJob.Reschedule("job-id", TimeSpan.FromDays(1)); _client.Verify(x => x.ChangeState( "job-id", It.Is(state => state.EnqueueAt > DateTime.UtcNow), null)); } [Fact, GlobalLock] public void Reschedule_WithTimeSpan_WithFromState_ChangesStateOfAJobToScheduled_WithFromState() { Initialize(); BackgroundJob.Reschedule("job-id", TimeSpan.FromDays(1), FailedState.StateName); _client.Verify(x => x.ChangeState( "job-id", It.Is(state => state.EnqueueAt > DateTime.UtcNow), FailedState.StateName)); } [Fact, GlobalLock] public void Reschedule_WithDateTimeOffset_ChangesStateOfAJobToScheduled() { var now = DateTimeOffset.Now; Initialize(); BackgroundJob.Reschedule("job-id", now); _client.Verify(x => x.ChangeState( "job-id", It.Is(state => state.EnqueueAt == now.UtcDateTime), null)); } [Fact, GlobalLock] public void Reschedule_WithDateTimeOffset_WithFromState_ChangesStateOfAJobToScheduled_WithFromState() { var now = DateTimeOffset.Now; Initialize(); BackgroundJob.Reschedule("job-id", now, FailedState.StateName); _client.Verify(x => x.ChangeState( "job-id", It.Is(state => state.EnqueueAt == now.UtcDateTime), FailedState.StateName)); } [Fact, GlobalLock(Reason = "Accesses to BJ.ClientFactory, JS.Current")] public void ClientFactory_HasDefaultValue_ThatReturns() { BackgroundJob.ClientFactory = null; JobStorage.Current = new Mock().Object; var client = BackgroundJob.ClientFactory(); Assert.NotNull(client); } private void Initialize() { BackgroundJob.ClientFactory = () => _client.Object; } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public void Method() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/BackgroundJobServerFacts.cs ================================================ namespace Hangfire.Core.Tests { public class BackgroundJobServerFacts { /*private readonly Mock _storage; private readonly Mock _supervisor; private readonly Mock _serverMock; private readonly BackgroundJobServerOptions _options; public BackgroundJobServerFacts() { _storage = new Mock(); _options = new BackgroundJobServerOptions(); _supervisor = new Mock(); _serverMock = new Mock(_options, _storage.Object) { CallBase = true }; _serverMock.Setup(x => x.GetBootstrapTask()).Returns(_supervisor.Object); } [Fact] public void Ctor_ThrowsAnException_WhenOptionsValueIsNull() { var exception = Assert.Throws( () => new BackgroundJobServer(null, _storage.Object)); Assert.Equal("options", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new BackgroundJobServer(_options, null)); Assert.Equal("storage", exception.ParamName); } [Fact, GlobalLock(Reason = "Uses JobStorage.Current instance")] public void Ctor_HasDefaultValue_ForStorage() { JobStorage.Current = new Mock().Object; Assert.DoesNotThrow(() => StartServer( () => new BackgroundJobServer(_options))); } [Fact] public void Ctor_HasDefaultValue_ForOptions() { Assert.DoesNotThrow(() => StartServer( () => new BackgroundJobServer(_storage.Object))); } [Fact, GlobalLock(Reason = "Uses JobStorage.Current instance")] public void Ctor_HasDefaultValues_ForAllParameters() { JobStorage.Current = new Mock().Object; Assert.DoesNotThrow(() => StartServer( () => new BackgroundJobServer())); } [Fact] public void Ctor_StartsTheBootstrapSupervisor() { var instance = _serverMock.Object; _supervisor.Verify(x => x.Start()); } [Fact] public void Dispose_DisposesBootstrapSupervisor() { _serverMock.Object.Dispose(); _supervisor.Verify(x => x.Dispose()); } [Fact] public void GetBootstrapSupervisor_ReturnsBootstrapper_WrappedWithAutomaticRetry() { // Arrange var server = CreateServer(); // Act var supervisor = server.GetBootstrapTask(); // Assert Assert.NotNull(supervisor); var wrapper = ((ServerSupervisor) supervisor).Component; Assert.IsType(wrapper); Assert.IsType(((AutomaticRetryServerComponentWrapper)wrapper).InnerComponent); } [Fact] public void GetSupervisors_ContainsDefaultComponents_WrappedTo_AutomaticRetryServerComponentWrapper() { // Arrange var server = CreateServer(); // Act var supervisors = server.GetSupervisors(); // Assert var componentTypes = supervisors.OfType() .Select(x => x.Component) .Cast() .Select(x => x.InnerComponent) .Select(x => x.GetType()) .ToArray(); Assert.Contains(typeof(Worker), componentTypes); Assert.Contains(typeof(ServerHeartbeat), componentTypes); Assert.Contains(typeof(ServerWatchdog), componentTypes); Assert.Contains(typeof(DelayedJobSchedulerFacts), componentTypes); } [Fact] public void GetSupervisors_ContainsStorageComponents_WrappedTo_AutomaticRetryServerComponentWrapper() { // Arrange var storageComponent = new Mock(); _storage.Setup(x => x.GetComponents()).Returns(new[] { storageComponent.Object }); var server = CreateServer(); // Act var supervisors = server.GetSupervisors(); // Assert var components = supervisors.OfType() .Select(x => x.Component) .Cast() .Select(x => x.InnerComponent) .ToArray(); Assert.Contains(storageComponent.Object, components); } private BackgroundJobServer CreateServer() { return new BackgroundJobServer(_options, _storage.Object); } private void StartServer(Func createFunc) { using (createFunc()) { } }*/ } } ================================================ FILE: tests/Hangfire.Core.Tests/CaptureCultureAttributeFacts.cs ================================================ using System.Globalization; using Xunit; namespace Hangfire.Core.Tests { public class CaptureCultureAttributeFacts { private readonly CreateContextMock _context; private readonly PerformContextMock _perform; private readonly CultureInfo _culture; private readonly CultureInfo _uiCulture; public CaptureCultureAttributeFacts() { _context = new CreateContextMock(); _perform = new PerformContextMock(); _culture = new CultureInfo("th-TH"); _uiCulture = new CultureInfo("vi-VN"); SetCurrentCulture(CultureInfo.InvariantCulture); SetCurrentUICulture(CultureInfo.InvariantCulture); _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentCulture")) .Returns($"\"{_culture.Name}\""); _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentUICulture")) .Returns($"\"{_uiCulture.Name}\""); } [Fact] public void Ctor_SetsDefaultCulturesToNull_ByDefault() { var attribute = new CaptureCultureAttribute(); Assert.Null(attribute.DefaultCultureName); Assert.Null(attribute.DefaultUICultureName); } [Fact] public void Ctor_AllowsToUseNulls_AsDefaultCultureValues() { var attribute = new CaptureCultureAttribute(null, null); Assert.Null(attribute.DefaultCultureName); Assert.Null(attribute.DefaultUICultureName); } [DataCompatibilityRangeFact] public void OnCreating_SetsCultureRelated_Parameters() { // Arrange var attribute = new CaptureCultureAttribute(); SetCurrentCulture(_culture); SetCurrentUICulture(_uiCulture); // Act attribute.OnCreating(_context.GetCreatingContext()); // Assert Assert.Equal(_context.Object.Parameters["CurrentCulture"], _culture.Name); Assert.Equal(_context.Object.Parameters["CurrentUICulture"], _uiCulture.Name); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_180)] public void OnCreating_ExplicitlySetsUICultureName_EvenWhenItIsEqualToTheCultureNameOne_WithCompatibilityVersion170AndBelow() { // Arrange var attribute = new CaptureCultureAttribute(null, null, captureDefault: false); SetCurrentCulture(_culture); SetCurrentUICulture(_culture); // Act attribute.OnCreating(_context.GetCreatingContext()); // Assert Assert.Equal(_context.Object.Parameters["CurrentCulture"], _culture.Name); Assert.Equal(_context.Object.Parameters["CurrentUICulture"], _culture.Name); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_180)] public void OnCreating_DoesNotSetUICultureName_WhenItIsEqualToTheCultureNameOne_WithCompatibilityVersion180AndAbove() { // Arrange var attribute = new CaptureCultureAttribute(null, null, captureDefault: true); SetCurrentCulture(_culture); SetCurrentUICulture(_culture); // Act attribute.OnCreating(_context.GetCreatingContext()); // Assert Assert.Equal(_context.Object.Parameters["CurrentCulture"], _culture.Name); Assert.False(_context.Object.Parameters.ContainsKey("CurrentUICulture")); } [DataCompatibilityRangeFact] public void OnCreating_SetsCultureNames_EvenWhenTheyEqualToTheDefaultOnes_WhenCaptureDefaultEnabled() { // Arrange SetCurrentCulture(_culture); SetCurrentUICulture(_uiCulture); var attribute = new CaptureCultureAttribute(_culture.Name, _uiCulture.Name, captureDefault: true); // Act attribute.OnCreating(_context.GetCreatingContext()); // Assert Assert.Equal(_context.Object.Parameters["CurrentCulture"], _culture.Name); Assert.Equal(_context.Object.Parameters["CurrentUICulture"], _uiCulture.Name); } [DataCompatibilityRangeFact] public void OnCreating_DoesNotSetCultureNames_WhenTheyEqualToTheDefaultOnes_WhenCaptureDefaultDisabled() { // Arrange SetCurrentCulture(_culture); SetCurrentUICulture(_uiCulture); var attribute = new CaptureCultureAttribute(_culture.Name, _uiCulture.Name, captureDefault: false); // Act attribute.OnCreating(_context.GetCreatingContext()); // Assert Assert.False(_context.Object.Parameters.ContainsKey("CurrentCulture")); Assert.False(_context.Object.Parameters.ContainsKey("CurrentUICulture")); } [Fact] public void OnCreated_DoesNotThrow_NotImplementedException() { // Arrange var attribute = new CaptureCultureAttribute(); // Act attribute.OnCreated(_context.GetCreatedContext("id")); // Assert – does not throw } [Fact] public void OnPerforming_ReadsCorrespondingJobParameters_AndSetCurrentCultures() { // Arrange var attribute = new CaptureCultureAttribute(); // Act attribute.OnPerforming(_perform.GetPerformingContext()); // Assert Assert.Equal(_culture, CultureInfo.CurrentCulture); Assert.Equal(_uiCulture, CultureInfo.CurrentUICulture); } [Fact] public void OnPerforming_UsesTheSameCultureForUI_WhenCultureIsSetButUICultureIsMissing() { // Arrange _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentUICulture")) .Returns((string)null); var attribute = new CaptureCultureAttribute(); // Act attribute.OnPerforming(_perform.GetPerformingContext()); // Assert Assert.Equal(_culture, CultureInfo.CurrentCulture); Assert.Equal(_culture, CultureInfo.CurrentUICulture); } [Fact] public void OnPerforming_UsesDefaultCultures_WhenCorrespondingJobParametersAreMissing() { // Arrange _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentCulture")) .Returns((string)null); _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentUICulture")) .Returns((string)null); var attribute = new CaptureCultureAttribute("en-US", "en-GB"); // Act attribute.OnPerforming(_perform.GetPerformingContext()); // Assert Assert.Equal("en-US", CultureInfo.CurrentCulture.Name); Assert.Equal("en-GB", CultureInfo.CurrentUICulture.Name); } [Fact] public void OnPerforming_UsesTheSameDefaultCultures_WhenCorrespondingJobParametersAreMissing_AndOnlyOneIsSpecified() { // Arrange _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentCulture")) .Returns((string)null); _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentUICulture")) .Returns((string)null); var attribute = new CaptureCultureAttribute("en-US"); // Act attribute.OnPerforming(_perform.GetPerformingContext()); // Assert Assert.Equal("en-US", CultureInfo.CurrentCulture.Name); Assert.Equal("en-US", CultureInfo.CurrentUICulture.Name); } [Fact] public void OnPerforming_DoesNotUseDefaultCultureAsDefaultUICulture_WhenCorrespondingJobParametersAreMissing_AndExplicitNullValueIsUsed() { // Arrange _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentCulture")) .Returns((string)null); _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentUICulture")) .Returns((string)null); var attribute = new CaptureCultureAttribute("en-US", null); // Act attribute.OnPerforming(_perform.GetPerformingContext()); // Assert Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentUICulture); } [Fact] public void OnPerforming_DoesNotSetAnything_WhenBothJobParametersMissing_AndDefaultCulturesNotSet() { // Arrange _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentCulture")) .Returns((string)null); _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentUICulture")) .Returns((string)null); var attribute = new CaptureCultureAttribute(); // Act attribute.OnPerforming(_perform.GetPerformingContext()); // Assert Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentCulture); Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentUICulture); } [Fact] public void OnPerforming_DoesNotSetAnything_InCaseOfCultureNotFoundException() { // Arrange _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentCulture")) .Returns("\"xx-XX\""); _perform.Connection .Setup(x => x.GetJobParameter(_perform.BackgroundJob.Id, "CurrentUICulture")) .Returns("\"yy-YY\""); var attribute = new CaptureCultureAttribute(); // Act attribute.OnPerforming(_perform.GetPerformingContext()); // Assert if (CultureInfo.CurrentCulture.Name != "xx-XX") Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentCulture); if (CultureInfo.CurrentUICulture.Name != "yy-YY") Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentUICulture); } [Fact] public void OnPerformed_ResetsCurrentCultures_ToTheirOriginalValues() { // Arrange var attribute = new CaptureCultureAttribute(); attribute.OnPerforming(_perform.GetPerformingContext()); // Act attribute.OnPerformed(_perform.GetPerformedContext()); // Assert Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentCulture); Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentUICulture); } [Fact] public void OnPerformed_DoesNotThrow_WhenCanNotRestoreOriginalCultures() { // Arrange var attribute = new CaptureCultureAttribute(); // Act attribute.OnPerformed(_perform.GetPerformedContext()); // Assert – does not throw } private static void SetCurrentCulture(CultureInfo value) { #if !NETCOREAPP1_0 System.Threading.Thread.CurrentThread.CurrentCulture = value; #else CultureInfo.CurrentCulture = value; #endif } // ReSharper disable once InconsistentNaming private static void SetCurrentUICulture(CultureInfo value) { #if !NETCOREAPP1_0 System.Threading.Thread.CurrentThread.CurrentUICulture = value; #else CultureInfo.CurrentUICulture = value; #endif } } } ================================================ FILE: tests/Hangfire.Core.Tests/Client/BackgroundJobFactoryFacts.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Moq.Sequences; using Xunit; // ReSharper disable PossibleNullReferenceException // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Client { public class BackgroundJobFactoryFacts { private readonly Mock _context; private readonly IList _filters; private readonly Mock _filterProvider; private readonly Mock _innerFactory; private readonly BackgroundJobMock _backgroundJob; public BackgroundJobFactoryFacts() { var storage = new Mock(); var connection = new Mock(); var state = new Mock(); _backgroundJob = new BackgroundJobMock(); _context = new Mock(storage.Object, connection.Object, _backgroundJob.Job, state.Object) { CallBase = true }; _filters = new List(); _filterProvider = new Mock(); _filterProvider.Setup(x => x.GetFilters(It.IsNotNull())).Returns( _filters.Select(f => new JobFilter(f, JobFilterScope.Type, null))); _innerFactory = new Mock(); _innerFactory.Setup(x => x.Create(_context.Object)).Returns(_backgroundJob.Object); } [Fact] public void Ctor_ThrowsAnException_WhenFilterProviderIsNull() { var exception = Assert.Throws( () => new BackgroundJobFactory(null, _innerFactory.Object)); Assert.Equal("filterProvider", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenInnerFactoryIsNull() { var exception = Assert.Throws( () => new BackgroundJobFactory(_filterProvider.Object, null)); Assert.Equal("innerFactory", exception.ParamName); } [Fact] public void Run_ThrowsAnException_WhenContextIsNull() { var factory = CreateFactory(); var exception = Assert.Throws( () => factory.Create(null)); Assert.Equal("context", exception.ParamName); } [Fact] public void Run_CallsInnerFactory_ToCreateAJob() { var factory = CreateFactory(); factory.Create(_context.Object); _innerFactory.Verify( x => x.Create(_context.Object), Times.Once); } [Fact] public void Run_ReturnsJobIdentifier() { var factory = CreateFactory(); var result = factory.Create(_context.Object); Assert.Equal(_backgroundJob.Id, result.Id); } [Fact] public void Run_DoesNotCatchExceptions() { _innerFactory.Setup(x => x.Create(It.IsAny())) .Throws(); var factory = CreateFactory(); Assert.Throws(() => factory.Create(_context.Object)); } [Fact] public void Run_CallsExceptionFilter_OnException() { // Arrange var filter = new Mock(); _filters.Add(filter.Object); _innerFactory.Setup(x => x.Create(It.IsAny())) .Throws(); var factory = CreateFactory(); // Act Assert.Throws( () => factory.Create(_context.Object)); // Assert filter.Verify(x => x.OnClientException( It.IsNotNull())); } [Fact, Sequence] public void Run_CallsExceptionFilters_InReverseOrder() { // Arrange var filter1 = new Mock(); var filter2 = new Mock(); filter2.Setup(x => x.OnClientException(It.IsAny())).InSequence(); filter1.Setup(x => x.OnClientException(It.IsAny())).InSequence(); _filters.Add(filter1.Object); _filters.Add(filter2.Object); _innerFactory .Setup(x => x.Create(It.IsAny())) .Throws(); var factory = CreateFactory(); // Act Assert.Throws( () => factory.Create(_context.Object)); // Assert - see the `SequenceAttribute` class. } [Fact] public void Run_EatsException_WhenItWasHandlerByFilter_AndReturnsNullJobIdentifier() { // Arrange _innerFactory.Setup(x => x.Create(It.IsAny())) .Throws(); var filter = new Mock(); filter.Setup(x => x.OnClientException(It.IsAny())) .Callback((ClientExceptionContext x) => x.ExceptionHandled = true); _filters.Add(filter.Object); var factory = CreateFactory(); // Act var jobId = factory.Create(_context.Object); Assert.Null(jobId); } [Fact, Sequence] public void Run_CallsClientFilters_BeforeAndAfterTheCreationOfAJob() { // Arrange var filter = new Mock(); _filters.Add(filter.Object); filter.Setup(x => x.OnCreating(It.IsNotNull())).InSequence(); _innerFactory.Setup(x => x.Create(It.IsAny())) .InSequence(); filter.Setup(x => x.OnCreated(It.IsNotNull())).InSequence(); var factory = CreateFactory(); // Act factory.Create(_context.Object); // Assert - see the `SequenceAttribute` class. } [Fact, Sequence] public void Run_WrapsFilterCalls_OneIntoAnother() { // Arrange var outerFilter = new Mock(); var innerFilter = new Mock(); _filters.Add(outerFilter.Object); _filters.Add(innerFilter.Object); outerFilter.Setup(x => x.OnCreating(It.IsAny())).InSequence(); innerFilter.Setup(x => x.OnCreating(It.IsAny())).InSequence(); innerFilter.Setup(x => x.OnCreated(It.IsAny())).InSequence(); outerFilter.Setup(x => x.OnCreated(It.IsAny())).InSequence(); var factory = CreateFactory(); // Act factory.Create(_context.Object); // Assert - see the `SequenceAttribute` class. } [Fact] public void Run_DoesNotCallBoth_CreateJob_And_OnCreated_WhenFilterCancelsThis_AndReturnsNullJobIdentifier() { // Arrange var filter = new Mock(); _filters.Add(filter.Object); filter.Setup(x => x.OnCreating(It.IsAny())) .Callback((CreatingContext x) => x.Canceled = true); var factory = CreateFactory(); // Act var jobId = factory.Create(_context.Object); // Assert Assert.Null(jobId); _innerFactory.Verify( x => x.Create(It.IsAny()), Times.Never); filter.Verify(x => x.OnCreated(It.IsAny()), Times.Never); } [Fact] public void Run_TellsOuterFilter_AboutTheCancellationOfCreation() { // Arrange var outerFilter = new Mock(); var innerFilter = new Mock(); _filters.Add(outerFilter.Object); _filters.Add(innerFilter.Object); innerFilter.Setup(x => x.OnCreating(It.IsAny())) .Callback((CreatingContext context) => context.Canceled = true); var factory = CreateFactory(); // Act factory.Create(_context.Object); // Assert outerFilter.Verify(x => x.OnCreated(It.Is(context => context.Canceled))); } [Fact] public void Run_DoesNotCall_CreateJob_And_OnCreated_WhenExceptionOccured_DuringCreatingPhase() { // Arrange var filter = new Mock(); _filters.Add(filter.Object); filter.Setup(x => x.OnCreating(It.IsAny())) .Throws(); var factory = CreateFactory(); // Act Assert.Throws( () => factory.Create(_context.Object)); // Assert _innerFactory.Verify( x => x.Create(It.IsAny()), Times.Never); filter.Verify(x => x.OnCreated(It.IsAny()), Times.Never); } [Fact] public void Run_TellsFiltersAboutException_WhenItIsOccured_DuringTheCreationOfAJob() { // Arrange var filter = new Mock(); _filters.Add(filter.Object); var exception = new InvalidOperationException(); _innerFactory.Setup(x => x.Create(It.IsAny())) .Throws(exception); var factory = CreateFactory(); // Act Assert.Throws( () => factory.Create(_context.Object)); // Assert filter.Verify(x => x.OnCreated(It.Is( context => context.Exception == exception))); } [Fact] public void Run_TellsOuterFilters_AboutAllExceptions() { // Arrange var outerFilter = new Mock(); var innerFilter = new Mock(); _filters.Add(outerFilter.Object); _filters.Add(innerFilter.Object); var exception = new InvalidOperationException(); _innerFactory.Setup(x => x.Create(It.IsAny())) .Throws(exception); var factory = CreateFactory(); // Act Assert.Throws( () => factory.Create(_context.Object)); outerFilter.Verify(x => x.OnCreated(It.Is(context => context.Exception == exception))); } [Fact] public void Run_DoesNotThrow_HandledExceptions_AndReturnsNullJobIdentifier() { // Arrange var filter = new Mock(); _filters.Add(filter.Object); var exception = new InvalidOperationException(); _innerFactory.Setup(x => x.Create(It.IsAny())) .Throws(exception); filter.Setup(x => x.OnCreated(It.Is(context => context.Exception == exception))) .Callback((CreatedContext x) => x.ExceptionHandled = true); var factory = CreateFactory(); // Act var jobId = factory.Create(_context.Object); // Assert Assert.Null(jobId); } [Fact] public void Run_TellsOuterFilter_EvenAboutHandledException() { // Arrange var outerFilter = new Mock(); var innerFilter = new Mock(); _filters.Add(outerFilter.Object); _filters.Add(innerFilter.Object); _innerFactory.Setup(x => x.Create(It.IsAny())) .Throws(); innerFilter.Setup(x => x.OnCreated(It.IsAny())) .Callback((CreatedContext x) => x.ExceptionHandled = true); var factory = CreateFactory(); // Act factory.Create(_context.Object); // Assert outerFilter.Verify(x => x.OnCreated(It.Is(context => context.Exception != null))); } [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void TestMethod() { } private BackgroundJobFactory CreateFactory() { return new BackgroundJobFactory(_filterProvider.Object, _innerFactory.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Client/ClientExceptionContextFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.Client { public class ClientExceptionContextFacts { private readonly CreateContext _createContext; public ClientExceptionContextFacts() { var storage = new Mock(); var connection = new Mock(); var job = Job.FromExpression(() => TestMethod()); var state = new Mock(); _createContext = new CreateContext( storage.Object, connection.Object, job, state.Object); } [Fact] public void Ctor_ThrowsAnException_WhenCreateContextIsNull() { Assert.Throws( () => new ClientExceptionContext(null, new Exception())); } [Fact] public void Ctor_ThrowsAnException_WhenExceptionIsNull() { var exception = Assert.Throws( () => new ClientExceptionContext(_createContext, null)); Assert.Equal("exception", exception.ParamName); } [Fact] public void Ctor_CorrectlySets_AllProperties() { var exception = new Exception(); var context = new ClientExceptionContext(_createContext, exception); Assert.Same(exception, context.Exception); Assert.False(context.ExceptionHandled); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void TestMethod() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/Client/CoreBackgroundJobFactoryFacts.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable PossibleNullReferenceException // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Client { public class CoreBackgroundJobFactoryFacts { private const string JobId = "jobId"; private readonly Mock _stateMachine; private readonly CreateContextMock _context; private readonly Mock _transaction; public CoreBackgroundJobFactoryFacts() { _stateMachine = new Mock(); _context = new CreateContextMock(); _transaction = new Mock(); _transaction.Setup(x => x.CreateJob( It.IsNotNull(), It.IsNotNull>(), It.IsAny(), It.IsAny())).Returns(JobId); _context.Connection.Setup(x => x.CreateExpiredJob( It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns(JobId); _context.Connection.Setup(x => x.GetJobData(JobId)).Returns(new JobData()); _context.Connection.Setup(x => x.CreateWriteTransaction()) .Returns(_transaction.Object); } [Fact] public void Ctor_ThrowsAnException_WhenStateMachineIsNull() { var exception = Assert.Throws( () => new CoreBackgroundJobFactory(null)); Assert.Equal("stateMachine", exception.ParamName); } [Fact] public void Create_ThrowsAnException_WhenContextIsNull() { var factory = CreateFactory(); var exception = Assert.Throws( () => factory.Create(null)); Assert.Equal("context", exception.ParamName); } [Fact] public void Create_ThrowsAnException_WhenJobQueueIsSet_ButStorageDoesNotSupportIt() { // Arrange _context.Storage.Setup(x => x.HasFeature(JobStorageFeatures.JobQueueProperty)).Returns(false); _context.Job = Job.FromExpression(() => Method(), "some-queue"); var factory = CreateFactory(); // Act & Assert Assert.Throws(() => factory.Create(_context.Object)); } [Fact] public void Create_ReturnsNull_WhenCreateExpiredJobReturnedNull() { _context.Connection .Setup(x => x.CreateExpiredJob(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Returns(null); var factory = CreateFactory(); var result = factory.Create(_context.Object); Assert.Null(result); } [Fact] public void CreateJob_CreatesExpiredJob() { _context.Object.Parameters.Add("Name", "Value"); var factory = CreateFactory(); factory.Create(_context.Object); _context.Connection.Verify(x => x.CreateExpiredJob( _context.Job, It.Is>(d => d["Name"] == "\"Value\""), It.IsAny(), It.IsAny())); } [Fact] public void CreateJob_ChangesTheStateOfACreatedJob() { var factory = CreateFactory(); factory.Create(_context.Object); _stateMachine.Verify(x => x.ApplyState( It.Is( sc => sc.BackgroundJob.Id == JobId && sc.BackgroundJob.Job == _context.Job && sc.NewState == _context.InitialState.Object && sc.OldStateName == null))); _transaction.Verify(x => x.Commit()); } [Fact] public void CreateJob_ReturnsNewJobId() { var factory = CreateFactory(); Assert.Equal(JobId, factory.Create(_context.Object).Id); } [Fact] public void Create_DoesNotRetryCreateExpiredJobMethod_ByDefault_AndThrowsAnException() { // Arrange _context.Connection .Setup(x => x.CreateExpiredJob(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Throws(); var factory = CreateFactory(); // Act Assert.Throws(() => factory.Create(_context.Object)); // Assert _context.Connection.Verify( x => x.CreateExpiredJob(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] public void Create_DoesNotRetryStateTransaction_ByDefault_AndThrowsAnException() { // Arrange _stateMachine.Setup(x => x.ApplyState(It.IsAny())).Throws(); var factory = CreateFactory(); // Act Assert.Throws(() => factory.Create(_context.Object)); // Assert _context.Connection.Verify( x => x.CreateExpiredJob(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Once); _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Once); _context.Connection.Verify(x => x.GetJobData(It.IsAny()), Times.Never); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void Create_IsResilientToASingleCreateExpiredJobFault_WhenRetriesEnabled() { // Arrange _context.Connection .SetupSequence(x => x.CreateExpiredJob(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Throws() .Returns(JobId); var factory = CreateFactory(retries: 1); // Act factory.Create(_context.Object); // Assert _context.Connection.Verify( x => x.CreateExpiredJob(It.IsNotNull(), It.IsNotNull>(), It.IsAny(), It.IsAny()), Times.Exactly(2)); _stateMachine.Verify(x => x.ApplyState(It.IsNotNull()), Times.Once); _context.Connection.Verify(x => x.GetJobData(It.IsAny()), Times.Never); _transaction.Verify(x => x.Commit(), Times.Once); } [Fact] public void Create_IsResilientToASingleStateMachineFault_WhenRetriesEnabled() { // Arrange _stateMachine.SetupSequence(x => x.ApplyState(It.IsAny())) .Throws() .Returns(_context.InitialState.Object); var factory = CreateFactory(retries: 1); // Act factory.Create(_context.Object); // Assert _context.Connection.Verify( x => x.CreateExpiredJob(It.IsNotNull(), It.IsNotNull>(), It.IsAny(), It.IsAny()), Times.Once); _stateMachine.Verify(x => x.ApplyState(It.IsNotNull()), Times.Exactly(2)); _context.Connection.Verify(x => x.GetJobData(JobId), Times.Once); _transaction.Verify(x => x.Commit(), Times.Once); } [Fact] public void Create_DoesNotInitializeJobTwice_WhenTransactionFaulted_WhenRetriesEnabled() { // Arrange _transaction.SetupSequence(x => x.Commit()) .Throws() .Pass(); _context.Connection.Setup(x => x.GetJobData(JobId)).Returns(new JobData { State = EnqueuedState.StateName }); var factory = CreateFactory(retries: 1); // Act factory.Create(_context.Object); // Assert _context.Connection.Verify( x => x.CreateExpiredJob(It.IsNotNull(), It.IsNotNull>(), It.IsAny(), It.IsAny()), Times.Once); _stateMachine.Verify(x => x.ApplyState(It.IsNotNull()), Times.Once); _context.Connection.Verify(x => x.GetJobData(JobId), Times.Once); _transaction.Verify(x => x.Commit(), Times.Once); } [Fact] public void Create_ThrowsAnException_WhenJobDataReturnsNull_OnStateTransactionRetry() { // Arrange _transaction.SetupSequence(x => x.Commit()) .Throws() .Pass(); _context.Connection.Setup(x => x.GetJobData(It.IsAny())).Returns((JobData)null); var factory = CreateFactory(retries: 1); // Act var exception = Assert.Throws(() => factory.Create(_context.Object)); // Assert Assert.IsType(exception.InnerExceptions[0]); Assert.IsType(exception.InnerExceptions[1]); _context.Connection.Verify( x => x.CreateExpiredJob(It.IsNotNull(), It.IsNotNull>(), It.IsAny(), It.IsAny()), Times.Once); _stateMachine.Verify(x => x.ApplyState(It.IsNotNull()), Times.Once); _transaction.Verify(x => x.Commit(), Times.Once); } [Fact] public void Create_ThrowsAnException_AndLeavesJobUninitialized_WhenAllRetryAttemptsExhausted_WhenCallingCreateExpiredJob() { // Arrange _context.Connection .SetupSequence(x => x.CreateExpiredJob(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Throws() .Throws(); var factory = CreateFactory(retries: 1); // Act var exception = Assert.Throws(() => factory.Create(_context.Object)); // Assert Assert.IsType(exception.InnerExceptions[0]); Assert.IsType(exception.InnerExceptions[1]); _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Never); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void Create_ThrowsAnException_AndLeavesJobAsIs_WhenAllRetryAttemptsExhausted_WithFaultyStateTransaction() { // Arrange _stateMachine.SetupSequence(x => x.ApplyState(It.IsAny())) .Throws() .Returns(_context.InitialState.Object); _transaction.SetupSequence(x => x.Commit()) .Throws() .Pass(); var factory = CreateFactory(retries: 1); // Act var exception = Assert.Throws(() => factory.Create(_context.Object)); // Assert Assert.IsType(exception.InnerExceptions[0]); Assert.IsType(exception.InnerExceptions[1]); _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Exactly(2)); _transaction.Verify(x => x.Commit(), Times.Once); } private CoreBackgroundJobFactory CreateFactory(int? retries = null) { var factory = new CoreBackgroundJobFactory(_stateMachine.Object); if (retries.HasValue) factory.RetryAttempts = retries.Value; return factory; } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Method() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/Client/CreateContextFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable ObjectCreationAsStatement // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Client { public class CreateContextFacts { private readonly Job _job; private readonly Mock _state; private readonly Mock _connection; private readonly Mock _storage; public CreateContextFacts() { _job = Job.FromExpression(() => Method()); _state = new Mock(); _connection = new Mock(); _storage = new Mock(); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new CreateContext(null, _connection.Object, _job, _state.Object)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenConnectionIsNull() { var exception = Assert.Throws( () => new CreateContext(_storage.Object, null, _job, _state.Object)); Assert.Equal("connection", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenJobIsNull() { var exception = Assert.Throws( () => new CreateContext(_storage.Object, _connection.Object, null, _state.Object)); Assert.Equal("job", exception.ParamName); } [Fact] public void Ctor_DoesNotThrowAnException_WhenStateIsNull() { // Does not throw new CreateContext(_storage.Object, _connection.Object, _job, null); } [Fact] public void Ctor_CorrectlyInitializes_AllProperties() { var context = CreateContext(); Assert.Same(_storage.Object, context.Storage); Assert.Same(_connection.Object, context.Connection); Assert.Same(_job, context.Job); Assert.Same(_state.Object, context.InitialState); Assert.NotNull(context.Items); Assert.NotNull(context.Parameters); } [Fact] public void CopyCtor_CopiesItemsDictionary_FromTheGivenContext() { var context = CreateContext(); var contextCopy = new CreateContext(context); Assert.Same(context.Items, contextCopy.Items); Assert.Same(context.Parameters, contextCopy.Parameters); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Method() { } private CreateContext CreateContext() { return new CreateContext(_storage.Object, _connection.Object, _job, _state.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Client/CreatedContextFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute #pragma warning disable 618 namespace Hangfire.Core.Tests.Client { public class CreatedContextFacts { private readonly Exception _exception; private readonly BackgroundJobMock _backgroundJob; public CreatedContextFacts() { _exception = new Exception(); _backgroundJob = new BackgroundJobMock(); } [Fact] public void Ctor_ThrowsAnException_WhenCreateContextIsNull() { Assert.Throws( () => new CreatedContext(null, _backgroundJob.Object, false, null)); } [Fact] public void Ctor_CorrectlySetsAllProperties() { var context = CreateContext(); Assert.True(context.Canceled); Assert.Same(_exception, context.Exception); Assert.Equal(_backgroundJob.Id, context.JobId); } [Fact] public void SetJobParameter_ThrowsAnException_WhenParameterNameIsNull() { var context = CreateContext(); var exception = Assert.Throws( () => context.SetJobParameter(null, null)); Assert.Equal("name", exception.ParamName); } [Fact] public void SetJobParameter_ThrowsAnException_AfterCreateJobWasCalled() { // TODO: incorrect test. var context = CreateContext(); Assert.Throws( () => context.SetJobParameter("name", "value")); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void TestMethod() { } private CreatedContext CreateContext() { var storage = new Mock(); var connection = new Mock(); var job = Job.FromExpression(() => TestMethod()); var state = new Mock(); var createContext = new CreateContext(storage.Object, connection.Object, job, state.Object); return new CreatedContext(createContext, _backgroundJob.Object, true, _exception); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Client/CreatingContextFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.Client { public class CreatingContextFacts { [Fact] public void Ctor_ThrowsAnException_WhenContextIsNull() { Assert.Throws( () => new CreatingContext(null)); } [Fact] public void Ctor_CanceledProperty_IsFalseByDefault() { var context = CreateContext(); Assert.False(context.Canceled); } [Fact] public void SetJobParameter_ThrowsAnException_WhenParameterNameIsNull() { var context = CreateContext(); var exception = Assert.Throws( () => context.SetJobParameter(null, null)); Assert.Equal("name", exception.ParamName); } [Fact] public void SetJobParameter_AcceptsNullValues() { var context = CreateContext(); // Does noto throw context.SetJobParameter("name", null); } [Fact] public void SetJobParameter_CanBeCalledTwice_WithTheSameName() { var context = CreateContext(); context.SetJobParameter("name", null); // Does not throw context.SetJobParameter("name", null); } [Fact] public void GetJobParameter_ThrowsAnException_WhenParameterNameIsNull() { var context = CreateContext(); Assert.Throws( () => context.GetJobParameter(null)); } [Fact] public void GetJobParameter_ReturnsDefaultValue_IfParameterDoesNotExists() { var context = CreateContext(); Assert.Equal(default(int), context.GetJobParameter("one")); Assert.Equal(default(string), context.GetJobParameter("two")); } [Fact] public void GetJobParameter_ReturnsTheValue_ThatWasSetByTheCorrespondingMethod() { var context = CreateContext(); context.SetJobParameter("name", "value"); Assert.Equal("value", context.GetJobParameter("name")); } [Fact] public void GetJobParameter_ReturnsTheValue_OfTheSpecifiedParameterNameOnly() { var context = CreateContext(); context.SetJobParameter("name1", "value1"); context.SetJobParameter("name2", "value2"); Assert.Equal("value1", context.GetJobParameter("name1")); } [Fact] public void GetJobParameter_ReturnsTheFreshestValue_WhenTwoSetOperationsPerformed() { var context = CreateContext(); context.SetJobParameter("name", "oldValue"); context.SetJobParameter("name", "newValue"); Assert.Equal("newValue", context.GetJobParameter("name")); } [Fact] public void GetJobParameter_ThrowsAnException_WhenParameterCouldNotBeDeserialized() { var context = CreateContext(); context.SetJobParameter("name", "value"); Assert.Throws( () => context.GetJobParameter("name")); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void TestMethod() { } private static CreatingContext CreateContext() { var storage = new Mock(); var connection = new Mock(); var job = Job.FromExpression(() => TestMethod()); var state = new Mock(); var createContext = new CreateContext(storage.Object, connection.Object, job, state.Object); return new CreatingContext(createContext); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/CancellationTokenExtentionsFacts.cs ================================================ using System; using System.Diagnostics; using System.Threading; using Hangfire.Common; using Xunit; namespace Hangfire.Core.Tests.Common { public class CancellationTokenExtentionsFacts { private readonly CancellationTokenSource _cts = new CancellationTokenSource(); [Fact] public void GetCancellationEvent_ReturnsSomething() { var cancellationEvent = _cts.Token.GetCancellationEvent(); Assert.NotNull(cancellationEvent); Assert.NotNull(cancellationEvent.WaitHandle); } [Fact] public void Wait_PerformsWait_NotLessThanTheSpecifiedTime() { var stopwatch = Stopwatch.StartNew(); var result = _cts.Token.Wait(TimeSpan.FromSeconds(1)); stopwatch.Stop(); Assert.False(result); Assert.True(stopwatch.Elapsed >= TimeSpan.FromMilliseconds(900), $"Elapsed: {stopwatch.Elapsed}"); } [Fact] public void Wait_DoesNotPerformWait_WhenTokenIsCanceled() { _cts.Cancel(); var stopwatch = Stopwatch.StartNew(); var result = _cts.Token.Wait(TimeSpan.FromSeconds(1)); stopwatch.Stop(); Assert.True(result); Assert.True(stopwatch.Elapsed < TimeSpan.FromMilliseconds(900), $"Elapsed: {stopwatch.Elapsed}"); } [Fact] public void WaitOrThrow_DoesNotThrow_WhenTokenIsNotCanceled() { _cts.Token.WaitOrThrow(TimeSpan.Zero); Assert.False(_cts.Token.IsCancellationRequested); } [Fact] public void WaitOrThrow_ThrowsAnException_WhenTokenIsCanceled() { _cts.Cancel(); var exception = Assert.Throws( () => _cts.Token.WaitOrThrow(TimeSpan.FromSeconds(1))); Assert.Equal(_cts.Token, exception.CancellationToken); Assert.True(exception.CancellationToken.IsCancellationRequested); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobArgumentFacts.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; using Moq; using Newtonsoft.Json; using Xunit; #pragma warning disable 618 namespace Hangfire.Core.Tests.Common { public class JobArgumentFacts { private readonly Mock _activator; private readonly Mock _token; public JobArgumentFacts() { _activator = new Mock(); _activator.Setup(x => x.ActivateJob(It.IsAny())) .Returns(() => new JobArgumentFacts()); _token = new Mock(); } private const Boolean BooleanValue = true; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Boolean value) { Assert.Equal(BooleanValue, value); } [Fact] public void BooleanArguments_AreBeingCorrectlyDeserialized() { CreateAndPerform(BooleanValue); } private const Byte ByteValue = 142; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Byte value) { Assert.Equal(ByteValue, value); } [Fact] public void ByteValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(ByteValue); } private const SByte SByteValue = -111; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(SByte value) { Assert.Equal(SByteValue, value); } [Fact] public void SByteValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(SByteValue); } private const Char CharValue = Char.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Char value) { Assert.Equal(CharValue, value); } [Fact] public void CharValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(CharValue); } private const Decimal DecimalValue = Decimal.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Decimal value) { Assert.Equal(DecimalValue, value); } [Fact] public void DecimalValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(DecimalValue); } private const Double DoubleValue = 3.14159265359D; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Double value) { Assert.Equal(DoubleValue, value); } [Fact] public void DoubleValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(DoubleValue); } private const Single SingleValue = 3.1415F; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Single value) { Assert.Equal(SingleValue, value); } [Fact] public void SingleValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(SingleValue); } private const Int32 Int32Value = Int32.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Int32 value) { Assert.Equal(Int32Value, value); } [Fact] public void Int32Values_AreBeingCorrectlyDeserialized() { CreateAndPerform(Int32Value); } private const UInt32 UInt32Value = UInt32.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(UInt32 value) { Assert.Equal(UInt32Value, value); } [Fact] public void UInt32Values_AreBeingCorrectlyDeserialized() { CreateAndPerform(UInt32Value); } private const Int64 Int64Value = Int64.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Int64 value) { Assert.Equal(Int64Value, value); } [Fact] public void Int64Values_AreBeingCorrectlyDeserialized() { CreateAndPerform(Int64Value); } #if !NETCOREAPP1_0 private const UInt64 UInt64Value = UInt64.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(UInt64 value) { Assert.Equal(UInt64Value, value); } [Fact] public void UInt64Values_AreBeingCorrectlyDeserialized() { CreateAndPerform(UInt64Value); } #endif private const Int16 Int16Value = Int16.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Int16 value) { Assert.Equal(Int16Value, value); } [Fact] public void Int16Values_AreBeingCorrectlyDeserialized() { CreateAndPerform(Int16Value); } private const UInt16 UInt16Value = UInt16.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(UInt16 value) { Assert.Equal(UInt16Value, value); } [Fact] public void UInt16Values_AreBeingCorrectlyDeserialized() { CreateAndPerform(UInt16Value); } private const String StringValue = "jkashdgfa$%^&"; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(String value) { Assert.Equal(StringValue, value); } [Fact] public void StringValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(StringValue); } private static readonly TimeSpan TimeSpanValue = TimeSpan.FromDays(1); [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(TimeSpan value) { Assert.Equal(TimeSpanValue, value); } [Fact] public void TimeSpanValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(TimeSpanValue); } private static readonly Object ObjectValue = "Hellojkadg"; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Object value) { Assert.Equal(ObjectValue, value); } [Fact] public void ObjectValues_AreBeingDeserializedAsStrings() { CreateAndPerform(ObjectValue); } private static readonly DateTimeOffset DateTimeOffsetValue = new DateTimeOffset(new DateTime(2012, 12, 12), TimeSpan.FromHours(1)); [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(DateTimeOffset value) { Assert.Equal(DateTimeOffsetValue, value); } [Fact] public void DateTimeOffsetValues_AreBeingDeserializedCorrectly() { // Don't run this test on Mono – https://bugzilla.xamarin.com/show_bug.cgi?id=25158 if (Type.GetType("Mono.Runtime") == null) { CreateAndPerform(DateTimeOffsetValue); } } #if !NETCOREAPP1_0 private static readonly CultureInfo CultureInfoValue = new CultureInfo("ru-RU"); [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(CultureInfo value) { Assert.Equal(CultureInfoValue, value); } [Fact] public void CultureInfoValues_AreBeingDeserializedCorrectly() { CreateAndPerform(CultureInfoValue); } #endif private const DayOfWeek EnumValue = DayOfWeek.Saturday; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(DayOfWeek value) { Assert.Equal(EnumValue, value); } [Fact] public void EnumValues_AreBeingDeserializedCorrectly() { CreateAndPerform(EnumValue); } private static readonly Guid GuidValue = Guid.NewGuid(); [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Guid value) { Assert.Equal(GuidValue, value); } [Fact] public void GuidValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(GuidValue); } private static readonly Uri UriValue = new Uri("https://example.com", UriKind.Absolute); [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Uri value) { Assert.Equal(UriValue, value); } [Fact] public void UriValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(UriValue); } private static readonly Int64? NotNullNullableValue = Int64.MaxValue; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Int64? value) { Assert.Equal(NotNullNullableValue, value); } [Fact] public void NotNullNullableValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(NotNullNullableValue); } private static readonly Int32? NullNullableValue = null; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Int32? value) { Assert.Equal(NullNullableValue, value); } [Fact] public void NullNullableValues_AreBeingCorrectlyDeserialized() { CreateAndPerform(NullNullableValue); } private static readonly string[] ArrayValue = { "Hello", "world" }; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(string[] value) { Assert.Equal(ArrayValue, value); } [Fact] public void ArrayValues_AreBeingCorrectlyDeserialized_FromJson() { CreateAndPerform(ArrayValue, true); } private static readonly List ListValue = new List { DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddDays(1) }; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(List value) { Assert.Equal(ListValue, value); } [Fact] public void ListValues_AreBeingCorrectlyDeserialized_FromJson() { CreateAndPerform(ListValue, true); } private static readonly Dictionary DictionaryValue = new Dictionary { { TimeSpan.FromSeconds(1), "123" }, { TimeSpan.FromDays(12), "376" } }; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(Dictionary value) { Assert.Equal(DictionaryValue, value); } [Fact] public void DictionaryValues_AreBeingCorrectlyDeserialized_FromJson() { CreateAndPerform(DictionaryValue, true); } public struct MyStruct { public Guid Id { get; set; } public string Name { get; set; } } private static readonly MyStruct CustomStructValue = new MyStruct { Id = Guid.NewGuid(), Name = "Hangfire" }; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(MyStruct value) { Assert.Equal(CustomStructValue, value); } [Fact] public void CustomStructValues_AreBeingCorrectlyDeserialized_FromJson() { CreateAndPerform(CustomStructValue, true); } #pragma warning disable 659 public class MyClass : IEquatable { public DateTime CreatedAt { get; set; } public bool Equals(MyClass other) { if (other == null) return false; return CreatedAt.Equals(other.CreatedAt); } public override bool Equals(object obj) { return Equals(obj as MyClass); } } #pragma warning restore 659 private static readonly MyClass CustomClassValue = new MyClass { CreatedAt = DateTime.UtcNow }; [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method(MyClass value) { Assert.Equal(CustomClassValue.CreatedAt, value.CreatedAt); } [Fact] public void CustomClassValues_AreBeingCorrectlyDeserialized_FromJson() { CreateAndPerform(CustomClassValue, true); } private static void CreateAndPerform(T argumentValue, bool checkJsonOnly = false) { var type = typeof(JobArgumentFacts); var methodInfo = type.GetMethod("Method", new[] { typeof(T) }); var serializationMethods = new List>>(); #if !NETCOREAPP1_0 if (!checkJsonOnly) { var converter = TypeDescriptor.GetConverter(typeof(T)); serializationMethods.Add(new Tuple>( "TypeDescriptor", () => converter.ConvertToInvariantString(argumentValue))); } #endif serializationMethods.Add(new Tuple>( "JSON", () => JsonConvert.SerializeObject(argumentValue))); foreach (var method in serializationMethods) { var data = new InvocationData( methodInfo?.DeclaringType?.AssemblyQualifiedName, methodInfo?.Name, JobHelper.ToJson(methodInfo?.GetParameters().Select(x => x.ParameterType).ToArray()), JobHelper.ToJson(new[] { method.Item2() })); var job = data.DeserializeJob(); Assert.Equal(argumentValue, job.Args[0]); } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobFacts.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Server; using Moq; using Newtonsoft.Json; using Xunit; // ReSharper disable LocalizableElement // ReSharper disable AssignNullToNotNullAttribute #pragma warning disable 618 namespace Hangfire.Core.Tests.Common { public class JobFacts { private static readonly DateTime SomeDateTime = new DateTime(2014, 5, 30, 12, 0, 0); private static bool _methodInvoked; private static bool _disposed; private readonly Type _type; private readonly MethodInfo _method; private readonly object[] _arguments; private readonly string _queue; private readonly Mock _activator; private readonly Mock _token; public JobFacts() { _type = typeof (JobFacts); _method = _type.GetMethod("StaticMethod"); _arguments = new object[0]; _queue = "critical"; _activator = new Mock { CallBase = true }; _token = new Mock(); } [Fact] public void Ctor_ThrowsAnException_WhenTheTypeIsNull() { var exception = Assert.Throws( // ReSharper disable once AssignNullToNotNullAttribute () => new Job(null, _method, _arguments)); Assert.Equal("type", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenTheMethodIsNull() { var exception = Assert.Throws( // ReSharper disable once AssignNullToNotNullAttribute () => new Job(_type, null, _arguments)); Assert.Equal("method", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenTheTypeDoesNotContainTheGivenMethod() { var exception = Assert.Throws( () => new Job(typeof(Job), _method, _arguments)); Assert.Equal("type", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenArgumentsArrayIsNull() { var exception = Assert.Throws( // ReSharper disable once AssignNullToNotNullAttribute () => new Job(_type, _method, (object[])null)); Assert.Equal("args", exception.ParamName); } [Fact] public void Ctor_DoesNotThrow_WhenQueueIsNull() { var job = new Job(_type, _method, _arguments, null); Assert.NotNull(job); } [Fact] public void Ctor_ThrowsAnException_WhenQueueValidationFails() { var exception = Assert.Throws( () => new Job(_type, _method, _arguments, "&^*%")); Assert.Equal("queue", exception.ParamName); } [Fact] public void Ctor_InitializesAllProperties() { var job = new Job(_type, _method, _arguments); Assert.Same(_type, job.Type); Assert.Same(_method, job.Method); Assert.True(_arguments.SequenceEqual(job.Arguments)); Assert.Null(job.Queue); } [Fact] public void Ctor_WithQueue_InitializesAllTheProperties() { var job = new Job(_type, _method, _arguments, _queue); Assert.Same(_type, job.Type); Assert.Same(_method, job.Method); Assert.True(_arguments.SequenceEqual(job.Args)); Assert.Equal(_queue, job.Queue); } [Fact] public void Ctor_HasDefaultValueForArguments() { var job = new Job(_type, _method); Assert.Empty(job.Arguments); } [Fact] public void Ctor_ThrowsAnException_WhenArgumentCountIsNotEqualToParameterCount() { var exception = Assert.Throws( () => new Job(_type, _method, new[] { "hello!" })); Assert.Contains("count", exception.Message); } [Fact] public void Ctor_ThrowsAnException_WhenMethodContains_UnassignedGenericTypeParameters() { var method = _type.GetMethod("GenericMethod"); Assert.Throws( () => new Job(_type, method, new[] { "hello!" })); } [Fact] public void Ctor_CanUsePropertyValues_OfAnotherJob_AsItsArguments() { var method = _type.GetMethod("MethodWithArguments"); var job = new Job(_type, method, "hello", 456); var anotherJob = new Job(job.Type, job.Method, job.Args); Assert.Equal(_type, anotherJob.Type); Assert.Equal(method, anotherJob.Method); Assert.Equal("hello", anotherJob.Args[0]); Assert.Equal(456, anotherJob.Args[1]); } [Fact] public void FromExpression_Action_ThrowsException_WhenNullExpressionProvided() { var exception = Assert.Throws( () => Job.FromExpression((Expression)null)); Assert.Equal("methodCall", exception.ParamName); } [Fact] public void FromExpression_Action_DoesNotThrowAnException_WhenNullQueueProvided() { var job = Job.FromExpression(() => Console.WriteLine(), null); Assert.NotNull(job); } [Fact] public void FromExpression_ThrowsAnException_WhenNewExpressionIsGiven() { Assert.Throws( // ReSharper disable once ObjectCreationAsStatement () => Job.FromExpression(() => new JobFacts())); } [Fact] public void FromExpression_Action_ReturnsTheJob() { var job = Job.FromExpression(() => Console.WriteLine()); Assert.Equal(typeof(Console), job.Type); Assert.Equal("WriteLine", job.Method.Name); Assert.Null(job.Queue); } [Fact] public void FromExpression_ActionWithQueue_ReturnsTheJobWithQueueSet() { var job = Job.FromExpression(() => Console.WriteLine(), "critical"); Assert.Equal(typeof(Console), job.Type); Assert.Equal("WriteLine", job.Method.Name); Assert.Equal("critical", job.Queue); } [Fact] public void FromExpression_Func_ThrowsException_WhenNullExpressionProvided() { var exception = Assert.Throws( () => Job.FromExpression((Expression>)null)); Assert.Equal("methodCall", exception.ParamName); } [Fact] public void FromExpression_Func_DoesNotThrowAnException_WhenNullQueueProvided() { var job = Job.FromExpression(() => AsyncMethod(), null); Assert.NotNull(job); } [Fact] public void FromExpression_Func_ReturnsTheJob() { var job = Job.FromExpression(() => AsyncMethod()); Assert.Equal(typeof(JobFacts), job.Type); Assert.Equal("AsyncMethod", job.Method.Name); Assert.Null(job.Queue); } [Fact] public void FromExpression_FuncWithQueue_ReturnsTheJobWithQueueSet() { var job = Job.FromExpression(() => AsyncMethod(), "critical"); Assert.Equal(typeof(JobFacts), job.Type); Assert.Equal("AsyncMethod", job.Method.Name); Assert.Equal("critical", job.Queue); } [Fact] public void FromExpression_ConvertsDateTimeRepresentation_ToIso8601Format() { var date = new DateTime(2014, 5, 30, 12, 0, 0, 777); var expected = date.ToString("o"); var job = Job.FromExpression(() => MethodWithDateTimeArgument(date)); Assert.Equal(expected, job.Arguments[0]); } [Fact] public void FromExpression_ConvertsArgumentsToJson() { var job = Job.FromExpression(() => MethodWithArguments("123", 1)); Assert.Equal("\"123\"", job.Arguments[0]); Assert.Equal("1", job.Arguments[1]); } [Fact] public void FromExpression_ConvertsObjectArgumentsToJson() { var job = Job.FromExpression(() => MethodWithObjectArgument("hello")); Assert.Equal("\"hello\"", job.Arguments[0]); } [Fact] public void FromExpression_ReturnValueDoesNotDepend_OnCurrentCulture() { var date = DateTime.UtcNow; CultureHelper.SetCurrentCulture("en-US"); var enJob = Job.FromExpression(() => MethodWithDateTimeArgument(date)); CultureHelper.SetCurrentCulture("ru-RU"); var ruJob = Job.FromExpression(() => MethodWithDateTimeArgument(date)); Assert.Equal(enJob.Arguments[0], ruJob.Arguments[0]); } [Fact] public void Ctor_ThrowsAnException_WhenMethodIsAsyncVoid() { var method = typeof(JobFacts).GetMethod(nameof(AsyncVoidMethod)); Assert.Throws( () => new Job(typeof(JobFacts), method, new string[0])); } [Fact] public void FromInstanceExpression_Action_ThrowsException_WhenNullExpressionIsProvided() { var exception = Assert.Throws( () => Job.FromExpression((Expression>)null)); Assert.Equal("methodCall", exception.ParamName); } [Fact] public void FromInstanceExpression_Action_DoesNotThrowAnException_WhenNullQueueIsProvided() { var job = Job.FromExpression(x => x.Method(), null); Assert.NotNull(job); } [Fact] public void FromInstanceExpression_Func_ThrowsException_WhenNullExpressionIsProvided() { var exception = Assert.Throws( () => Job.FromExpression((Expression>)null)); Assert.Equal("methodCall", exception.ParamName); } [Fact] public void FromInstanceExpression_Func_DoesNotThrowAnException_WhenNullQueueIsProvided() { var job = Job.FromExpression(x => x.FunctionReturningTask(), null); Assert.NotNull(job); } [Fact] public void FromInstanceExpression_ThrowsAnException_WhenNewExpressionIsGiven() { Assert.Throws( // ReSharper disable once ObjectCreationAsStatement () => Job.FromExpression(x => new JobFacts())); } [Fact] public void FromInstanceExpression_Action_ReturnsCorrectResult() { var job = Job.FromExpression(x => x.Method()); Assert.Equal(typeof(Instance), job.Type); Assert.Equal("Method", job.Method.Name); Assert.Null(job.Queue); } [Fact] public void FromInstanceExpression_ActionWithQueue_ReturnsCorrectResultWithQueueSet() { var job = Job.FromExpression(x => x.Method(), "critical"); Assert.Equal(typeof(Instance), job.Type); Assert.Equal("Method", job.Method.Name); Assert.Equal("critical", job.Queue); } [Fact] public void FromInstanceExpression_Func_ReturnsCorrectResult() { var job = Job.FromExpression(x => x.FunctionReturningTask()); Assert.Equal(typeof(Instance), job.Type); Assert.Equal("FunctionReturningTask", job.Method.Name); Assert.Null(job.Queue); } [Fact] public void FromInstanceExpression_FuncWithQueue_ReturnsCorrectResultWithQueueSet() { var job = Job.FromExpression(x => x.FunctionReturningTask(), "critical"); Assert.Equal(typeof(Instance), job.Type); Assert.Equal("FunctionReturningTask", job.Method.Name); Assert.Equal("critical", job.Queue); } [Fact] public void FromNonGenericExpression_InfersType_FromAGivenObject() { IDisposable instance = new Instance(); var job = Job.FromExpression(() => instance.Dispose()); Assert.Equal(typeof(Instance), job.Type); } [Fact] public void FromNonGenericExpression_InfersACorrectMethod_FromAGivenObject_WhenInterfaceTreeIsUsed() { IDisposable instance = new Instance(); var job = Job.FromExpression(() => instance.Dispose()); Assert.Equal(typeof(Instance), job.Method.DeclaringType); } [Fact] public void FromNonGenericExpression_ThrowsAnException_IfGivenObjectIsNull() { IDisposable instance = null; Assert.Throws( () => Job.FromExpression(() => instance.Dispose())); } [Fact] public void FromGenericExpression_InfersType_FromAGivenObject_AndHandlesAssignableParameters() { IServiceInterface service = new MyBaseClassService(); MyDerivedClass myClass = new MyDerivedClass(); var job = Job.FromExpression(() => service.MyMethod(myClass)); Assert.Equal(typeof(MyBaseClassService), job.Type); Assert.Equal("MyMethod", job.Method.Name); Assert.Equal(typeof(MyBaseClassService), job.Method.DeclaringType); } [Fact] public void FromScopedExpression_HandlesGenericMethods() { CommandDispatcher dispatcher = new CommandDispatcher(); var job = Job.FromExpression(() => dispatcher.DispatchTyped(123)); Assert.Equal(typeof(CommandDispatcher), job.Type); Assert.Equal(typeof(CommandDispatcher), job.Method.DeclaringType); } [Fact] public void FromScopedExpression_HandlesMethodsDeclaredInBaseClasse() { DerivedInstance instance = new DerivedInstance(); var job = Job.FromExpression(() => instance.Method()); Assert.Equal(typeof(DerivedInstance), job.Type); Assert.Equal(typeof(Instance), job.Method.DeclaringType); } [Fact] public void FromScopedExpression_ThrowsWhenExplicitInterfaceImplementationIsPassed() { IService service = new ServiceImpl(); Assert.Throws(() => Job.FromExpression(() => service.Method())); } public interface IService { void Method(); } public class ServiceImpl : IService { void IService.Method() { } } [Fact] public void Ctor_ThrowsAnException_WhenMethodContainsReferenceParameter() { string test = null; Assert.Throws( () => Job.FromExpression(() => MethodWithReferenceParameter(ref test))); } [Fact] public void Ctor_ThrowsAnException_WhenMethodContainsOutputParameter() { string test; Assert.Throws( () => Job.FromExpression(() => MethodWithOutputParameter(out test))); } [Fact] public void Ctor_ThrowsAnException_WhenMethodIsNotPublic() { Assert.Throws( () => Job.FromExpression(() => PrivateMethod())); } [Fact] public void Ctor_ThrowsAnException_WhenMethodParametersContainADelegate() { Assert.Throws( () => Job.FromExpression(() => DelegateMethod(() => Console.WriteLine("Hey delegate!")))); } [Fact] public void Ctor_ThrowsAnException_WhenMethodParametersContainAnExpression() { Assert.Throws( () => Job.FromExpression(() => ExpressionMethod(() => Console.WriteLine("Hey expression!")))); } [Fact] public void Perform_ThrowsAnException_WhenActivatorIsNull() { var job = Job.FromExpression(() => StaticMethod()); var exception = Assert.Throws( () => job.Perform(null, _token.Object)); Assert.Equal("activator", exception.ParamName); } [Fact] public void Perform_ThrowsAnException_WhenCancellationTokenIsNull() { var job = Job.FromExpression(() => StaticMethod()); var exception = Assert.Throws( () => job.Perform(_activator.Object, null)); Assert.Equal("cancellationToken", exception.ParamName); } [Fact, StaticLock] public void Perform_CanInvokeStaticMethods() { _methodInvoked = false; var job = Job.FromExpression(() => StaticMethod()); job.Perform(_activator.Object, _token.Object); Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_CanInvokeInstanceMethods() { _methodInvoked = false; var job = Job.FromExpression(x => x.Method()); job.Perform(_activator.Object, _token.Object); Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_DisposesDisposableInstance_AfterPerformance() { _disposed = false; var job = Job.FromExpression(x => x.Method()); job.Perform(_activator.Object, _token.Object); Assert.True(_disposed); } [Fact, StaticLock] public void Perform_PassesArguments_ToACallingMethod() { // Arrange _methodInvoked = false; var job = Job.FromExpression(() => MethodWithArguments("hello", 5)); // Act job.Perform(_activator.Object, _token.Object); // Assert - see the `MethodWithArguments` method. Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_PassesObjectArguments_ToACallingMethod() { // Arrange _methodInvoked = false; var job = Job.FromExpression(() => MethodWithObjectArgument("5")); // Act job.Perform(_activator.Object, _token.Object); // Assert - see the `MethodWithArguments` method. Assert.True(_methodInvoked); } #if !NETCOREAPP1_0 [Fact, StaticLock] public void Perform_PassesCorrectDateTime_IfItWasSerialized_UsingTypeConverter() { // Arrange _methodInvoked = false; var typeConverter = TypeDescriptor.GetConverter(typeof (DateTime)); var convertedDate = typeConverter.ConvertToInvariantString(SomeDateTime); var type = typeof (JobFacts); var method = type.GetMethod("MethodWithDateTimeArgument"); var job = new Job(type, method, new[] { convertedDate }); // Act job.Perform(_activator.Object, _token.Object); // Assert - see also the `MethodWithDateTimeArgument` method. Assert.True(_methodInvoked); } #endif [Fact, StaticLock] public void Perform_PassesCorrectDateTime_IfItWasSerialized_UsingOldFormat() { // Arrange _methodInvoked = false; var convertedDate = SomeDateTime.ToString("MM/dd/yyyy HH:mm:ss.ffff"); var type = typeof(JobFacts); var method = type.GetMethod("MethodWithDateTimeArgument"); var job = new Job(type, method, new[] { convertedDate }); // Act job.Perform(_activator.Object, _token.Object); // Assert - see also the `MethodWithDateTimeArgument` method. Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_PassesCorrectDateTimeArguments() { // Arrange _methodInvoked = false; var job = Job.FromExpression(() => MethodWithDateTimeArgument(SomeDateTime)); // Act job.Perform(_activator.Object, _token.Object); // Assert - see also the `MethodWithDateTimeArgument` method. Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_WorksCorrectly_WithNullValues() { // Arrange _methodInvoked = false; var job = Job.FromExpression(() => NullArgumentMethod(null)); // Act job.Perform(_activator.Object, _token.Object); // Assert - see also `NullArgumentMethod` method. Assert.True(_methodInvoked); } [Fact] public void Perform_ThrowsPerformanceException_WhenActivatorThrowsAnException() { var exception = new InvalidOperationException(); _activator.Setup(x => x.ActivateJob(It.IsAny())).Throws(exception); var job = Job.FromExpression(() => InstanceMethod()); var thrownException = Assert.Throws( () => job.Perform(_activator.Object, _token.Object)); Assert.Same(exception, thrownException.InnerException); } [Fact] public void Perform_ThrowsPerformanceException_WhenActivatorReturnsNull() { _activator.Setup(x => x.ActivateJob(It.IsNotNull())).Returns(null); var job = Job.FromExpression(() => InstanceMethod()); var thrownException = Assert.Throws( () => job.Perform(_activator.Object, _token.Object)); Assert.IsType(thrownException.InnerException); } [Fact] public void Ctor_ThrowsJsonReaderException_OnArgumentsDeserializationFailure() { var type = typeof (JobFacts); var method = type.GetMethod("MethodWithDateTimeArgument"); Assert.Throws( () => new Job(type, method, new []{ JobHelper.ToJson("sdfa") })); } [Fact, StaticLock] public void Perform_ThrowsPerformanceException_OnDisposalFailure() { _methodInvoked = false; var job = Job.FromExpression(x => x.Method()); var exception = Assert.Throws( () => job.Perform(_activator.Object, _token.Object)); Assert.True(_methodInvoked); Assert.NotNull(exception.InnerException); } [Fact] public void Perform_ThrowsPerformanceException_WithUnwrappedInnerException() { var job = Job.FromExpression(() => ExceptionMethod()); var thrownException = Assert.Throws( () => job.Perform(_activator.Object, _token.Object)); Assert.IsType(thrownException.InnerException); Assert.Equal("exception", thrownException.InnerException.Message); } [Fact] public void Perform_ThrowsPerformanceException_WhenAMethodThrowsTaskCanceledException() { var job = Job.FromExpression(() => TaskCanceledExceptionMethod()); var thrownException = Assert.Throws( () => job.Perform(_activator.Object, _token.Object)); Assert.IsType(thrownException.InnerException); } [Fact] public void Perform_RethrowsOperationCanceledException_WhenShutdownTokenIsCanceled() { // Arrange var job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _token.Setup(x => x.ShutdownToken).Returns(new CancellationToken(true)); _token.Setup(x => x.ThrowIfCancellationRequested()).Throws(); // Act & Assert Assert.Throws(() => job.Perform(_activator.Object, _token.Object)); } [Fact] public void Run_RethrowsTaskCanceledException_WhenShutdownTokenIsCanceled() { // Arrange var job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _token.Setup(x => x.ShutdownToken).Returns(new CancellationToken(true)); _token.Setup(x => x.ThrowIfCancellationRequested()).Throws(); // Act & Assert Assert.Throws(() => job.Perform(_activator.Object, _token.Object)); } [Fact] public void Run_RethrowsJobAbortedException() { // Arrange var job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _token.Setup(x => x.ShutdownToken).Returns(CancellationToken.None); _token.Setup(x => x.ThrowIfCancellationRequested()).Throws(); // Act & Assert Assert.Throws(() => job.Perform(_activator.Object, _token.Object)); } [Fact] public void Run_ThrowsJobPerformanceException_InsteadOfOperationCanceled_WhenShutdownWasNOTInitiated() { // Arrange var job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _token.Setup(x => x.ShutdownToken).Returns(CancellationToken.None); _token.Setup(x => x.ThrowIfCancellationRequested()).Throws(); // Act & Assert Assert.Throws(() => job.Perform(_activator.Object, _token.Object)); } [Fact] public void Perform_ReturnsValue_WhenCallingFunctionReturningValue() { var job = Job.FromExpression(x => x.FunctionReturningValue()); var result = job.Perform(_activator.Object, _token.Object); Assert.Equal("Return value", result); } [Fact] public void GetTypeFilterAttributes_ReturnsCorrectAttributes() { var job = Job.FromExpression(x => x.Method()); var nonCachedAttributes = job.GetTypeFilterAttributes(false).ToArray(); var cachedAttributes = job.GetTypeFilterAttributes(true).ToArray(); Assert.Single(nonCachedAttributes); Assert.Single(cachedAttributes); Assert.True(nonCachedAttributes[0] is TestTypeAttribute); Assert.True(cachedAttributes[0] is TestTypeAttribute); } [Fact] public void GetMethodFilterAttributes_ReturnsCorrectAttributes() { var job = Job.FromExpression(x => x.Method()); var nonCachedAttributes = job.GetMethodFilterAttributes(false).ToArray(); var cachedAttributes = job.GetMethodFilterAttributes(true).ToArray(); Assert.Single(nonCachedAttributes); Assert.Single(cachedAttributes); Assert.True(nonCachedAttributes[0] is TestMethodAttribute); Assert.True(cachedAttributes[0] is TestMethodAttribute); } private static void PrivateMethod() { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void MethodWithReferenceParameter(ref string a) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void MethodWithOutputParameter(out string a) { a = "hello"; } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void StaticMethod() { _methodInvoked = true; } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public void InstanceMethod() { _methodInvoked = true; } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void CancelableJob(IJobCancellationToken token) { token.ThrowIfCancellationRequested(); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void NullArgumentMethod(string[] argument) { _methodInvoked = true; Assert.Null(argument); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public void MethodWithArguments(string stringArg, int intArg) { _methodInvoked = true; Assert.Equal("hello", stringArg); Assert.Equal(5, intArg); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public void MethodWithObjectArgument(object argument) { _methodInvoked = true; Assert.Equal("5", argument); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] public void MethodWithCustomArgument(Instance argument) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public void MethodWithDateTimeArgument(DateTime argument) { _methodInvoked = true; Assert.Equal(SomeDateTime, argument); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void ExceptionMethod() { throw new InvalidOperationException("exception"); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void TaskCanceledExceptionMethod() { throw new TaskCanceledException(); } [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void GenericMethod(T arg) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public Task AsyncMethod() { var source = new TaskCompletionSource(); return source.Task; } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public async void AsyncVoidMethod() { await Task.Yield(); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public void DelegateMethod(Action action) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public void ExpressionMethod(Expression expression) { } private interface ICommandDispatcher { void DispatchTyped(TCommand command); } private sealed class CommandDispatcher : ICommandDispatcher { public void DispatchTyped(TCommand command) { } } [TestType] public class Instance : IDisposable { [TestMethod] public void Method() { _methodInvoked = true; } public void Dispose() { _disposed = true; } public string FunctionReturningValue() { return "Return value"; } public async Task FunctionReturningTask() { await Task.Yield(); } public async Task FunctionReturningValueTask() { await Task.Yield(); } public async Task FunctionReturningTaskResultingInString(bool continueOnCapturedContext) { await Task.Yield(); await Task.Delay(15).ConfigureAwait(continueOnCapturedContext); return FunctionReturningValue(); } public ValueTask FunctionReturningValueTaskResultingInString(bool continueOnCapturedContext) { return new ValueTask(FunctionReturningTaskResultingInString(continueOnCapturedContext)); } } public class DerivedInstance : Instance { } public class BrokenDispose : IDisposable { public void Method() { _methodInvoked = true; } public void Dispose() { throw new InvalidOperationException(); } } // ReSharper disable once UnusedTypeParameter public class JobClassWrapper : IDisposable where T : IDisposable { public void Dispose() { } } public class TestTypeAttribute : JobFilterAttribute { } public class TestMethodAttribute : JobFilterAttribute { } class MyBaseClass { public string BaseProp { get; set; } } class MyDerivedClass : MyBaseClass { public int MyProperty { get; set; } } interface IServiceInterface where T : MyBaseClass { Task MyMethod(T input); } class MyBaseClassService : IServiceInterface { public Task MyMethod(MyBaseClass input) { return Task.FromResult(true); } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobFilterAttributeFacts.cs ================================================ using System; using Hangfire.Common; using Moq; using Xunit; namespace Hangfire.Core.Tests.Common { public class JobFilterAttributeFacts { [Fact] public void SetOrder_ThrowsAnException_WhenValueIsLessThanDefaultOrder() { var filterAttribute = new Mock { CallBase = true }; Assert.Throws( () => filterAttribute.Object.Order = -2); } [Fact] public void TypeId_Property_IsNotIncludedIntoSerializedForm() { var attribute = new SampleJobAttribute(); var serialized = SerializationHelper.Serialize(attribute); Assert.DoesNotContain("TypeId", serialized); } [Fact] public void AllowMultiple_Property_IsNotIncludedIntoSerializedForm_SinceItIsGetOnlyProperty() { var attribute = new SampleJobAttribute(); var serialized = SerializationHelper.Serialize(attribute); Assert.DoesNotContain("AllowMultiple", serialized); } [Fact] public void Order_Property_IsNotIncludedIntoSerializedForm_WhenDefaultValueIsUsed() { var attribute = new SampleJobAttribute(); var serialized = SerializationHelper.Serialize(attribute); Assert.DoesNotContain("Order", serialized); } [Fact] public void Order_Property_IsIncludedIntoSerializedForm_WhenNonDefaultValueIsUsed() { var attribute = new SampleJobAttribute { Order = 555 }; var serialized = SerializationHelper.Serialize(attribute); Assert.Contains("\"Order\":555", serialized); } [Fact] public void Order_Property_ProperlyHandlesDefaultValue_WhenBeingDeserialized() { var attribute = SerializationHelper.Deserialize("{}"); Assert.Equal(-1, attribute.Order); } private sealed class SampleJobAttribute : JobFilterAttribute { } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobFilterAttributeFilterProviderFacts.cs ================================================ using System.Linq; using Hangfire.Common; using Xunit; namespace Hangfire.Core.Tests.Common { public class JobFilterAttributeFilterProviderFacts { [Fact] public void GetFilters_WithNullJob_ReturnsEmptyList() { // Arrange var provider = new JobFilterAttributeFilterProvider(); // Act var result = provider.GetFilters(null); // Assert Assert.Empty(result); } [MyFilter(Order = 2112)] // ReSharper disable once ClassNeverInstantiated.Local private class ClassWithTypeAttribute { public static void Method() { } } [Fact] public void GetFilters_IncludesAttributesOnClassType() { // Arrange var job = Job.FromExpression(() => ClassWithTypeAttribute.Method()); var provider = new JobFilterAttributeFilterProvider(); // Act var filter = provider.GetFilters(job).Single(); // Assert var attribute = filter.Instance as MyFilterAttribute; Assert.NotNull(attribute); Assert.Equal(JobFilterScope.Type, filter.Scope); Assert.Equal(2112, filter.Order); } // ReSharper disable once ClassNeverInstantiated.Local private class ClassWithActionAttribute { [MyFilter(Order = 1234)] public static void Method() { } } [Fact] public void GetFilters_IncludesAttributesOMethod() { // Arrange var job = Job.FromExpression(() => ClassWithActionAttribute.Method()); var provider = new JobFilterAttributeFilterProvider(); // Act var filter = provider.GetFilters(job).Single(); // Assert var attribute = filter.Instance as MyFilterAttribute; Assert.NotNull(attribute); Assert.Equal(JobFilterScope.Method, filter.Scope); Assert.Equal(1234, filter.Order); } private abstract class BaseClass { public void MyMethod() { } } [MyFilter] // ReSharper disable once ClassNeverInstantiated.Local private class DerivedClass : BaseClass { } [Fact] public void GetFilters_IncludesTypeAttributesFromDerivedTypeWhenMethodIsOnBaseClass() { // Arrange var job = Job.FromExpression(x => x.MyMethod()); var provider = new JobFilterAttributeFilterProvider(); // Act var filters = provider.GetFilters(job); // Assert Assert.NotNull(filters.Select(f => f.Instance).Cast().Single()); } private class MyFilterAttribute : JobFilterAttribute { } [Fact] public void GetFilters_RetrievesNonCachedAttributesWhenConfiguredNotTo() { // Arrange var job = Job.FromExpression(x => x.MyMethod()); var provider = new JobFilterAttributeFilterProvider(false); // Act var filters = provider.GetFilters(job); // Assert Assert.NotNull(filters.Select(f => f.Instance).Cast().Single()); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobFilterCollectionFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Hangfire.Client; using Hangfire.Common; using Hangfire.Server; using Hangfire.States; using Moq; using Xunit; namespace Hangfire.Core.Tests.Common { public class JobFilterCollectionFacts { private readonly JobFilterCollection _collection = new JobFilterCollection(); private readonly object _filterInstance = GetFilterInstance(); public static IEnumerable AddRejectsNonFilterInstancesData => new List { new object[] { "string" }, new object[] { 42 }, new object[] { new JobFilterCollectionFacts() }, }; [Fact] public void AddRejectsNonFilterInstances() { foreach (var instance in AddRejectsNonFilterInstancesData) { // Act + Assert Assert.Throws(() => _collection.Add(instance)); } } [Fact] public void AddAcceptsFilterInstances() { // Arrange var filters = new object[] { GetFilterInstance(), GetFilterInstance(), GetFilterInstance(), GetFilterInstance(), GetFilterInstance(), GetFilterInstance() }.ToList(); // Act filters.ForEach(f => _collection.Add(f)); // Assert Assert.Equal(filters, _collection.Select(i => i.Instance)); } [Fact] public void AddPlacesFilterInGlobalScope() { // Act _collection.Add(_filterInstance); // Assert JobFilter filter = Assert.Single(_collection); Assert.Same(_filterInstance, filter.Instance); Assert.Equal(JobFilterScope.Global, filter.Scope); Assert.Equal(-1, filter.Order); } [Fact] public void AddWithOrderPlacesFilterInGlobalScope() { // Act _collection.Add(_filterInstance, 42); // Assert JobFilter filter = Assert.Single(_collection); Assert.Same(_filterInstance, filter.Instance); Assert.Equal(JobFilterScope.Global, filter.Scope); Assert.Equal(42, filter.Order); } [Fact] public void ContainsFindsFilterByInstance() { // Arrange _collection.Add(_filterInstance); // Act bool result = _collection.Contains(_filterInstance); // Assert Assert.True(result); } [Fact] public void RemoveDeletesFilterByInstance() { // Arrange _collection.Add(_filterInstance); // Act _collection.Remove(_filterInstance); // Assert Assert.Empty(_collection); } [Fact] public void RemoveWithTypeDeletesFilterByType() { // Arrange _collection.Add(_filterInstance); // Act _collection.Remove(_filterInstance.GetType()); // Assert Assert.Empty(_collection); } [Fact] public void CollectionIsIFilterProviderWhichReturnsAllFilters() { // Arrange _collection.Add(_filterInstance); var provider = (IJobFilterProvider)_collection; // Act IEnumerable result = provider.GetFilters(null); // Assert JobFilter filter = Assert.Single(result); Assert.Same(_filterInstance, filter.Instance); } [Fact] public void Count_ReturnsNumberOfElements() { _collection.Add(_filterInstance); Assert.Equal(1, _collection.Count); } [Fact] public void Clear_RemovesAllElementsFromCollection() { _collection.Add(_filterInstance); _collection.Clear(); Assert.Equal(0, _collection.Count); } private static TFilter GetFilterInstance() where TFilter : class { return new Mock().Object; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobFilterFacts.cs ================================================ using System; using Hangfire.Common; using Moq; using Xunit; namespace Hangfire.Core.Tests.Common { public class JobFilterFacts { [Fact] public void GuardClause() { var exception = Assert.Throws( () => new JobFilter(null, JobFilterScope.Method, null)); Assert.Equal("instance", exception.ParamName); } [Fact] public void FilterDoesNotImplementIJobFilter() { // Arrange var filterInstance = new object(); // Act var filter = new JobFilter(filterInstance, JobFilterScope.Method, null); // Assert Assert.Same(filterInstance, filter.Instance); Assert.Equal(JobFilterScope.Method, filter.Scope); Assert.Equal(JobFilter.DefaultOrder, filter.Order); } [Fact] public void FilterImplementsIJobFilter() { // Arrange var filterInstance = new Mock(); filterInstance.SetupGet(f => f.Order).Returns(42); // Act var filter = new JobFilter(filterInstance.Object, JobFilterScope.Type, null); // Assert Assert.Same(filterInstance.Object, filter.Instance); Assert.Equal(JobFilterScope.Type, filter.Scope); Assert.Equal(42, filter.Order); } [Fact] public void ExplicitOrderOverridesIJobFilter() { // Arrange var filterInstance = new Mock(); filterInstance.SetupGet(f => f.Order).Returns(42); // Act var filter = new JobFilter(filterInstance.Object, JobFilterScope.Type, 2112); // Assert Assert.Same(filterInstance.Object, filter.Instance); Assert.Equal(JobFilterScope.Type, filter.Scope); Assert.Equal(2112, filter.Order); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobFilterProviderCollectionFacts.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Hangfire.Common; using Moq; using Xunit; namespace Hangfire.Core.Tests.Common { public class JobFilterProviderCollectionFacts { private readonly Job _job; public JobFilterProviderCollectionFacts() { _job = Job.FromExpression(() => Sample()); } [Fact] public void GetFilters_ReturnsNull_WhenJobIsNull() { var collection = new JobFilterProviderCollection(); var filters = collection.GetFilters(null); Assert.Empty(filters); } [Fact] public void GetFiltersUsesRegisteredProviders() { // Arrange var filter = new JobFilter(new Object(), JobFilterScope.Method, null); var provider = new Mock(MockBehavior.Strict); var collection = new JobFilterProviderCollection(provider.Object); provider.Setup(p => p.GetFilters(_job)).Returns(new[] { filter }); // Act IEnumerable result = collection.GetFilters(_job); // Assert Assert.Same(filter, result.Single()); } [Fact] public void GetFiltersSortsFiltersByOrderFirstThenScope() { // Arrange var actionFilter = new JobFilter(new Object(), JobFilterScope.Method, null); var controllerFilter = new JobFilter(new Object(), JobFilterScope.Type, null); var globalFilter = new JobFilter(new Object(), JobFilterScope.Global, null); var earlyActionFilter = new JobFilter(new Object(), JobFilterScope.Method, -100); var lateGlobalFilter = new JobFilter(new Object(), JobFilterScope.Global, 100); var provider = new Mock(MockBehavior.Strict); var collection = new JobFilterProviderCollection(provider.Object); provider.Setup(p => p.GetFilters(_job)) .Returns(new[] { actionFilter, controllerFilter, globalFilter, earlyActionFilter, lateGlobalFilter }); // Act JobFilter[] result = collection.GetFilters(_job).ToArray(); // Assert Assert.Equal(5, result.Length); Assert.Same(earlyActionFilter, result[0]); Assert.Same(globalFilter, result[1]); Assert.Same(controllerFilter, result[2]); Assert.Same(actionFilter, result[3]); Assert.Same(lateGlobalFilter, result[4]); } [Fact] public void GetFiltersSortsFiltersStablyAllowMultipleFalse() { // Arrange var objectFilter = new JobFilter(new Object(), JobFilterScope.Global, null); var allowMultipleFalseAttribute1 = new JobFilter(new AllowMultipleFalseAttribute(), JobFilterScope.Global, null); var allowMultipleFalseAttribute2 = new JobFilter(new AllowMultipleFalseAttribute(), JobFilterScope.Global, null); var provider = new Mock(MockBehavior.Strict); var collection = new JobFilterProviderCollection(provider.Object); // to prove instability we need at least 17 items in the list provider.Setup(p => p.GetFilters(_job)) .Returns(new[] { objectFilter, objectFilter, objectFilter, objectFilter, objectFilter, objectFilter, objectFilter, allowMultipleFalseAttribute1, objectFilter, objectFilter, objectFilter, allowMultipleFalseAttribute2, objectFilter, objectFilter, objectFilter, objectFilter, objectFilter }); // Act IEnumerable result = collection.GetFilters(_job); // Assert Assert.Same(allowMultipleFalseAttribute2, result.Last(item => item.Instance is AllowMultipleFalseAttribute)); } [AttributeUsage(AttributeTargets.All)] private class AllowMultipleFalseAttribute : JobFilterAttribute { } [Fact] public void GetFiltersIncludesLastFilterOnlyWithAttributeUsageAllowMultipleFalse() { // Arrange var globalFilter = new JobFilter(new AllowMultipleFalseAttribute(), JobFilterScope.Global, null); var controllerFilter = new JobFilter(new AllowMultipleFalseAttribute(), JobFilterScope.Type, null); var actionFilter = new JobFilter(new AllowMultipleFalseAttribute(), JobFilterScope.Method, null); var provider = new Mock(MockBehavior.Strict); var collection = new JobFilterProviderCollection(provider.Object); provider.Setup(p => p.GetFilters(_job)) .Returns(new[] { controllerFilter, actionFilter, globalFilter }); // Act IEnumerable result = collection.GetFilters(_job); // Assert Assert.Same(actionFilter, result.Single()); } [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] private class AllowMultipleTrueAttribute : JobFilterAttribute { } [Fact] public void GetFiltersIncludesAllFiltersWithAttributeUsageAllowMultipleTrue() { // Arrange var globalFilter = new JobFilter(new AllowMultipleTrueAttribute(), JobFilterScope.Global, null); var controllerFilter = new JobFilter(new AllowMultipleTrueAttribute(), JobFilterScope.Type, null); var actionFilter = new JobFilter(new AllowMultipleTrueAttribute(), JobFilterScope.Method, null); var provider = new Mock(MockBehavior.Strict); var collection = new JobFilterProviderCollection(provider.Object); provider.Setup(p => p.GetFilters(_job)) .Returns(new[] { controllerFilter, actionFilter, globalFilter }); // Act List result = collection.GetFilters(_job).ToList(); // Assert Assert.Same(globalFilter, result[0]); Assert.Same(controllerFilter, result[1]); Assert.Same(actionFilter, result[2]); } private class AllowMultipleCustomFilter : IJobFilter { public AllowMultipleCustomFilter(bool allowMultiple) { AllowMultiple = allowMultiple; } public bool AllowMultiple { get; } public int Order => -1; } [Fact] public void GetFiltersIncludesLastFilterOnlyWithCustomFilterAllowMultipleFalse() { // Arrange var globalFilter = new JobFilter(new AllowMultipleCustomFilter(false), JobFilterScope.Global, null); var controllerFilter = new JobFilter(new AllowMultipleCustomFilter(false), JobFilterScope.Type, null); var actionFilter = new JobFilter(new AllowMultipleCustomFilter(false), JobFilterScope.Method, null); var provider = new Mock(MockBehavior.Strict); var collection = new JobFilterProviderCollection(provider.Object); provider.Setup(p => p.GetFilters(_job)) .Returns(new[] { controllerFilter, actionFilter, globalFilter }); // Act IEnumerable result = collection.GetFilters(_job); // Assert Assert.Same(actionFilter, result.Single()); } [Fact] public void GetFiltersIncludesAllFiltersWithCustomFilterAllowMultipleTrue() { // Arrange var globalFilter = new JobFilter(new AllowMultipleCustomFilter(true), JobFilterScope.Global, null); var controllerFilter = new JobFilter(new AllowMultipleCustomFilter(true), JobFilterScope.Type, null); var actionFilter = new JobFilter(new AllowMultipleCustomFilter(true), JobFilterScope.Method, null); var provider = new Mock(MockBehavior.Strict); var collection = new JobFilterProviderCollection(provider.Object); provider.Setup(p => p.GetFilters(_job)) .Returns(new[] { controllerFilter, actionFilter, globalFilter }); // Act List result = collection.GetFilters(_job).ToList(); // Assert Assert.Same(globalFilter, result[0]); Assert.Same(controllerFilter, result[1]); Assert.Same(actionFilter, result[2]); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Sample() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobHelperFacts.cs ================================================ using System; #if NETCOREAPP1_0 using System.Reflection; #endif using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Storage; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; #pragma warning disable 618 // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Common { public class JobHelperFacts { private static readonly DateTime WellKnownDateTime = new DateTime(1988, 04, 20, 01, 12, 32, DateTimeKind.Utc); private const int WellKnownTimestamp = 577501952; private const long WellKnownMillisecondTimestamp = 577501952000; [DataCompatibilityRangeFact] public void ToJson_EncodesNullValueAsNull() { var result = JobHelper.ToJson(null); Assert.Null(result); } [DataCompatibilityRangeFact] public void ToJson_EncodesGivenValue_ToJsonString() { var result = JobHelper.ToJson("hello"); Assert.Equal("\"hello\"", result); } [DataCompatibilityRangeFact] public void FromJson_DecodesNullAsDefaultValue() { var stringResult = JobHelper.FromJson(null); var intResult = JobHelper.FromJson(null); Assert.Null(stringResult); Assert.Equal(0, intResult); } [DataCompatibilityRangeFact] public void FromJson_DecodesFromJsonString() { var result = JobHelper.FromJson("\"hello\""); Assert.Equal("hello", result); } [DataCompatibilityRangeFact] public void FromJson_ThrowsAnException_WhenTypeIsNull() { Assert.Throws(() => JobHelper.FromJson("1", null)); } [DataCompatibilityRangeFact] public void FromJson_WithType_DecodesFromJsonString() { var result = (string) JobHelper.FromJson("\"hello\"", typeof (string)); Assert.Equal("hello", result); } [DataCompatibilityRangeFact] public void FromJson_WithType_DecodesNullValue_ToNull() { var result = (string) JobHelper.FromJson(null, typeof (string)); Assert.Null(result); } [DataCompatibilityRangeFact] public void ToTimestamp_ReturnsUnixTimestamp_OfTheGivenDateTime() { var result = JobHelper.ToTimestamp( WellKnownDateTime); Assert.Equal(WellKnownTimestamp, result); } [DataCompatibilityRangeFact] public void FromTimestamp_ReturnsDateTime_ForGivenTimestamp() { var result = JobHelper.FromTimestamp(WellKnownTimestamp); Assert.Equal(WellKnownDateTime, result); } [DataCompatibilityRangeFact] public void ToMillisecondTimestamp_ReturnsTheCorrectResult() { var result = JobHelper.ToMillisecondTimestamp(WellKnownDateTime); Assert.Equal(WellKnownMillisecondTimestamp, result); } [DataCompatibilityRangeFact] public void FromMillisecondTimestamp_ReturnsTheCorrectDateTime() { var result = JobHelper.FromMillisecondTimestamp(WellKnownMillisecondTimestamp); Assert.Equal(WellKnownDateTime, result); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void SerializeDateTime_ReturnsString_InISO8601Format_In_Version_Pre_170() { var result = JobHelper.SerializeDateTime(WellKnownDateTime); Assert.Equal(WellKnownDateTime.ToString("O"), result); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializeDateTime_ReturnsString_WithMillisecondTimestamp_In_Version_170() { var result = JobHelper.SerializeDateTime(WellKnownDateTime); Assert.Equal(WellKnownMillisecondTimestamp.ToString(), result); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializeDateTime_ReturnsMillisecondTimestamp_ForRecentDates() { var dateTime = DateTime.UtcNow; var result = JobHelper.SerializeDateTime(dateTime); Assert.True(long.TryParse(result, out var timestamp)); Assert.Equal(JobHelper.ToMillisecondTimestamp(dateTime), timestamp); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializeDateTime_ReturnsMillisecondTimestamp_AtLeastUpTo2100() { var dateTime = new DateTime(2100, 01, 01, 00, 00, 00, DateTimeKind.Utc); var result = JobHelper.SerializeDateTime(dateTime); Assert.True(long.TryParse(result, out var timestamp)); Assert.Equal(JobHelper.ToMillisecondTimestamp(dateTime), timestamp); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializeDateTime_ReturnsISO8601String_ForConflictingRanges_WithSecondBasedTimestamps() { var dateTime = new DateTime(1975, 01, 01, 00, 00, 00, DateTimeKind.Utc); var result = JobHelper.SerializeDateTime(dateTime); Assert.False(long.TryParse(result, out _)); Assert.Equal(dateTime.ToString("O"), result); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializeDateTime_ReturnsISO8601String_WhenTimestampIsNotApplicable() { var dateTime = new DateTime(1900, 01, 01, 00, 00, 00, DateTimeKind.Utc); var result = JobHelper.SerializeDateTime(dateTime); Assert.False(long.TryParse(result, out _)); Assert.Equal(dateTime.ToString("O"), result); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializeDateTime_ReturnsISO8601String_WithMaxDateTime() { var dateTime = DateTime.MaxValue; var result = JobHelper.SerializeDateTime(dateTime); Assert.False(long.TryParse(result, out _)); Assert.Equal(dateTime.ToString("O"), result); } [DataCompatibilityRangeFact] public void DeserializeDateTime_CanDeserialize_Timestamps() { var result = JobHelper.DeserializeDateTime(WellKnownTimestamp.ToString()); Assert.Equal(WellKnownDateTime, result); } [DataCompatibilityRangeFact] public void DeserializeDateTime_CanDeserialize_ISO8601Format() { var result = JobHelper.DeserializeDateTime(WellKnownDateTime.ToString("o")); Assert.Equal(WellKnownDateTime, result); } [DataCompatibilityRangeFact] public void DeserializeDateTime_CanDeserialize_MillisecondTimestamp() { var result = JobHelper.DeserializeDateTime(WellKnownMillisecondTimestamp.ToString()); Assert.Equal(WellKnownDateTime, result); } [DataCompatibilityRangeFact] public void DeserializeNullableDateTime_ReturnsNull_IfNullOrEmptyStringGiven() { Assert.Null(JobHelper.DeserializeNullableDateTime("")); Assert.Null(JobHelper.DeserializeNullableDateTime(null)); } [DataCompatibilityRangeFact] public void DeserializeNullableDateTime_ReturnsCorrectValue_OnNonNullString() { var result = JobHelper.DeserializeNullableDateTime(WellKnownTimestamp.ToString()); Assert.Equal(WellKnownDateTime, result); } [DataCompatibilityRangeFact] public void FromJson_WithObjectType_DecodesFromJsonString() { var result = (ClassA)JobHelper.FromJson(@"{ ""PropertyA"": ""hello"" }", typeof(ClassA)); Assert.NotNull(result); Assert.Equal("hello", result.PropertyA); } [DataCompatibilityRangeFact] public void ForSerializeUseDefaultConfigurationOfJsonNet() { var result = JobHelper.ToJson(new ClassA("A")); Assert.Equal(@"{""PropertyA"":""A""}", result); } [DataCompatibilityRangeFact] public void ForSerializeCanUseCustomConfigurationOfJsonNet() { try { JobHelper.SetSerializerSettings(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); var result = JobHelper.ToJson(new ClassA("A")); Assert.Equal(@"{""propertyA"":""A""}", result); } finally { JobHelper.SetSerializerSettings(null); } } [DataCompatibilityRangeFact] public void ForDeserializeCanUseCustomConfigurationOfJsonNet() { try { JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects }); var result = (ClassA)JobHelper.FromJson(@"{ ""$type"": ""Hangfire.Core.Tests.Common.JobHelperFacts+ClassA, Hangfire.Core.Tests"", ""propertyA"":""A"" }"); Assert.Equal("A", result.PropertyA); } finally { JobHelper.SetSerializerSettings(null); } } [DataCompatibilityRangeFact] public void ForDeserializeCanUseCustomConfigurationOfJsonNetWithInvocationData() { try { JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, #if !NET452 && !NET461 && !NETCOREAPP1_0 TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple #endif }); var method = typeof (BackgroundJob).GetMethod("DoWork"); var args = new object[] { "123", "Test" }; var job = new Job(typeof(BackgroundJob), method, args); var invocationData = InvocationData.Serialize(job); var deserializedJob = invocationData.Deserialize(); Assert.Equal(typeof(BackgroundJob), deserializedJob.Type); Assert.Equal(method, deserializedJob.Method); Assert.Equal(args, deserializedJob.Args); } finally { JobHelper.SetSerializerSettings(null); } } [DataCompatibilityRangeFact] public void ForDeserializeWithGenericMethodCanUseCustomConfigurationOfJsonNet() { try { JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects }); var result = (ClassA)JobHelper.FromJson(@"{ ""$type"": ""Hangfire.Core.Tests.Common.JobHelperFacts+ClassA, Hangfire.Core.Tests"", ""propertyA"":""A"" }", typeof(IClass)); Assert.NotNull(result); Assert.Equal("A", result.PropertyA); } finally { JobHelper.SetSerializerSettings(null); } } private interface IClass { } private class ClassA : IClass { public ClassA(string propertyA) { PropertyA = propertyA; } public string PropertyA { get; } } private class BackgroundJob { [UsedImplicitly] public void DoWork(string workId, string message) { } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/JobLoadExceptionFacts.cs ================================================ using System; using Hangfire.Common; using Xunit; namespace Hangfire.Core.Tests.Common { public class JobLoadExceptionFacts { [Fact] public void Ctor_CreatesException_WithGivenMessageAnInnerException() { var innerException = new Exception(); var exception = new JobLoadException("1", innerException); Assert.Equal("1", exception.Message); Assert.Same(innerException, exception.InnerException); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/MethodInfoExtensionsFacts.cs ================================================ using System.Linq; using System.Reflection; using Hangfire.Common; using Xunit; namespace Hangfire.Core.Tests.Common { public class MethodInfoExtensionsFacts { [Fact] public void GetNormalizedName_ReturnsNormalizedName_ForRegularMethod() { var service = new RegularInterfaceImplementation(); var normalizedName = service.GetType().GetRuntimeMethods().First().GetNormalizedName(); Assert.Equal("Method", normalizedName); } [Fact] public void GetNormalizedName_ReturnsNormalizedName_ForExplicitlyImplementedMethod() { var service = new ExplicitInterfaceImplementation(); var normalizedName = service.GetType().GetRuntimeMethods().First().GetNormalizedName(); Assert.Equal("Method", normalizedName); } private interface IService { void Method(); } private class RegularInterfaceImplementation : IService { public void Method() { } } private class ExplicitInterfaceImplementation : IService { void IService.Method() { } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/SerializationHelperFacts.cs ================================================ using System; using System.Runtime.Serialization; using Hangfire.Common; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; namespace Hangfire.Core.Tests.Common { public class SerializationHelperFacts { [DataCompatibilityRangeFact] public void Serialize_ReturnsNull_WhenValueIsNull() { Assert.Null(SerializationHelper.Serialize((string)null)); } [DataCompatibilityRangeFact] public void Serialize_ReturnsCorrectResult_WhenValueIsString() { var result = SerializationHelper.Serialize("Simple string"); Assert.Equal("\"Simple string\"", result); } [DataCompatibilityRangeFact] public void Serialize_ReturnsCorrectValue_WhenValueIsCustomObject() { var result = SerializationHelper.Serialize(new ClassA("B")); Assert.Equal(@"{""PropertyA"":""B""}", result); } [DataCompatibilityRangeFact] public void Serialize_ReturnsCorrectJson_WhenOptionsIsTypedInternal() { var result = SerializationHelper.Serialize(new ClassA("B"), SerializationOption.TypedInternal); Assert.Equal(@"{""$type"":""Hangfire.Core.Tests.Common.SerializationHelperFacts+ClassA, Hangfire.Core.Tests"",""PropertyA"":""B""}", result); } [DataCompatibilityRangeFact, CleanSerializerSettings] public void Serialize_SerializesWithUserSettings_WhenOptionsIsUser() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); var result = SerializationHelper.Serialize(new ClassA("A"), SerializationOption.User); Assert.Equal(@"{""propertyA"":""A""}", result); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_SerializesWithUserSettings_WhenOptionsIsInternal_BeforeVersion170() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), }); var result = SerializationHelper.Serialize(new ClassA("A")); Assert.Equal(@"{""propertyA"":""A""}", result); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_SerializesWithDefaultSettingsWithTypeInformation_WhenOptionIsTypedInternal_BeforeVersion170() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, ContractResolver = new CamelCasePropertyNamesContractResolver() }); var result = SerializationHelper.Serialize(new ClassB { StringValue = "hi"}, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.Core.Tests.Common.SerializationHelperFacts+ClassB, Hangfire.Core.Tests\"," + "\"StringValue\":\"hi\",\"NullValue\":null,\"DefaultValue\":0,\"DateTimeValue\":null}", result); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_DoesNotSerializeWithUserSettings_WhenOptionsIsInternal_AfterVersion170() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); var result = SerializationHelper.Serialize(new ClassA("A")); Assert.Equal(@"{""PropertyA"":""A""}", result); } [DataCompatibilityRangeFact, CleanSerializerSettings] public void Serialize_DoesNotSerializeWithUserSettings_WhenOptionsIsTypedInternal() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); var result = SerializationHelper.Serialize(new ClassA("A"), SerializationOption.TypedInternal); Assert.Equal( @"{""$type"":""Hangfire.Core.Tests.Common.SerializationHelperFacts+ClassA, Hangfire.Core.Tests"",""PropertyA"":""A""}", result); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_ProducesObjectThatCanBeDeserialized_UsingJsonConvert_WithInternalSettings_BeforeVersion170() { // Arrange SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), TypeNameHandling = TypeNameHandling.All }); var initialObject = new ClassA("A"); // Act var result = SerializationHelper.Serialize(initialObject); var resultingObject = JsonConvert.DeserializeObject(result, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None }); // Assert Assert.Equal(initialObject.PropertyA, resultingObject.PropertyA); } #if !NET452 && !NET461 [DataCompatibilityRangeFact, CleanSerializerSettings] public void Serialize_JsonDefaultSettingsAffectResult_WhenOptionIs_User() { JsonConvert.DefaultSettings = () => new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, Binder = new CustomSerializerBinder(), DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, DateFormatString = "ddMMyyyy" }; var result = SerializationHelper.Serialize(new ClassB { StringValue = "B", DateTimeValue = new DateTime(1961, 4, 12) }, SerializationOption.User); Assert.Equal( "{\"$type\":\"HANGFIRE.CORE.TESTS.COMMON.SERIALIZATIONHELPERFACTS+CLASSB, someAssembly\",\"StringValue\":\"B\",\"DateTimeValue\":\"12041961\"}", result); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_JsonDefaultSettingsDoAffectResult_WhenOptionIs_Internal_BeforeVersion_170() { JsonConvert.DefaultSettings = () => new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, Binder = new CustomSerializerBinder(), DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, DateFormatString = "ddMMyyyy" }; var result = SerializationHelper.Serialize(new ClassB { StringValue = "B", DateTimeValue = new DateTime(1961, 4, 12) }); Assert.Equal( "{\"$type\":\"HANGFIRE.CORE.TESTS.COMMON.SERIALIZATIONHELPERFACTS+CLASSB, someAssembly\",\"StringValue\":\"B\",\"DateTimeValue\":\"12041961\"}", result); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_JsonDefaultSettingsDoNotAffectResult_WhenOptionIs_Internal_StartingFromVersion_170() { JsonConvert.DefaultSettings = () => new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include, Binder = new CustomSerializerBinder(), DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, DateFormatString = "ddMMyyyy" }; var result = SerializationHelper.Serialize(new ClassB { StringValue = "B", DateTimeValue = new DateTime(1961, 4, 12) }); Assert.Equal(@"{""StringValue"":""B"",""DateTimeValue"":""1961-04-12T00:00:00""}", result); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_JsonDefaultSettingsDoAffectResult_WhenOptionIs_TypedInternal_BeforeVersion_170() { JsonConvert.DefaultSettings = () => new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, Binder = new CustomSerializerBinder(), DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, DateFormatString = "ddMMyyyy" }; var result = SerializationHelper.Serialize( new ClassB { StringValue = "B", DateTimeValue = new DateTime(1961, 4, 12) }, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"HANGFIRE.CORE.TESTS.COMMON.SERIALIZATIONHELPERFACTS+CLASSB, someAssembly\",\"StringValue\":\"B\",\"DateTimeValue\":\"12041961\"}", result); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170), CleanSerializerSettings] public void Serialize_JsonDefaultSettingsDoNotAffectResult_WhenOptionIs_TypedInternal_StartingFromVersion_170() { JsonConvert.DefaultSettings = () => new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include, Binder = new CustomSerializerBinder(), DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, DateFormatString = "ddMMyyyy" }; var result = SerializationHelper.Serialize( new ClassB { StringValue = "B", DateTimeValue = new DateTime(1961, 4, 12) }, SerializationOption.TypedInternal); Assert.Equal(@"{""$type"":""Hangfire.Core.Tests.Common.SerializationHelperFacts+ClassB, Hangfire.Core.Tests"",""StringValue"":""B"",""DateTimeValue"":""1961-04-12T00:00:00""}", result); } #endif [DataCompatibilityRangeFact, CleanSerializerSettings] public void Serialize_WithSpecifiedType_DoesNotIncludeTypeProperty_WhenItEqualsToDeclared_AndTypeNameHandlingAutoIsUsed() { JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); var result = SerializationHelper.Serialize( new ClassA("hello"), typeof(ClassA), SerializationOption.User); Assert.Equal("{\"PropertyA\":\"hello\"}", result); } [DataCompatibilityRangeFact, CleanSerializerSettings] public void Serialize_WithSpecifiedType_IncludesTypeProperty_WhenItDoesNotEqualToDeclared_AndTypeNameHandlingAutoIsUsed() { JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); var result = SerializationHelper.Serialize( new ClassA("hello"), typeof(object), SerializationOption.User); Assert.Equal("{\"$type\":\"Hangfire.Core.Tests.Common.SerializationHelperFacts+ClassA, Hangfire.Core.Tests\",\"PropertyA\":\"hello\"}", result); } [DataCompatibilityRangeFact] public void Deserialize_ReturnsNull_WhenValueIsNull() { var result = SerializationHelper.Deserialize(null, typeof(string)); Assert.Null(result); } [DataCompatibilityRangeFact] public void Deserialize_ThrowsException_WhenTypeIsNull() { // ReSharper disable once AssignNullToNotNullAttribute var exception = Assert.Throws(() => SerializationHelper.Deserialize("someString", null)); Assert.Equal("type", exception.ParamName); } [DataCompatibilityRangeFact] public void Deserialize_ReturnsCorrectValue_WhenValueIsString() { var result = SerializationHelper.Deserialize("\"hello\"", typeof(string)); Assert.Equal("hello", result); } [DataCompatibilityRangeFact] public void Deserialize_ReturnsCorrectObject_WhenTypeIsCustomClass() { var valueJson = @"{""PropertyA"":""A""}"; var value = SerializationHelper.Deserialize(valueJson, typeof(ClassA)) as ClassA; Assert.NotNull(value); Assert.Equal("A", value.PropertyA); } [DataCompatibilityRangeFact] public void Deserialize_ReturnsCorrectObject_WhenOptionsIsTypedInternal() { var valueJson = @"{""$type"":""Hangfire.Core.Tests.Common.SerializationHelperFacts+ClassA, Hangfire.Core.Tests"",""PropertyA"":""A""}"; var value = SerializationHelper.Deserialize(valueJson, typeof(ClassA), SerializationOption.TypedInternal); var customObj = value as ClassA; Assert.NotNull(customObj); Assert.Equal("A", customObj.PropertyA); } [DataCompatibilityRangeFact, CleanSerializerSettings] //This test is here to check backward compatibility. Earlier user settings is used for serialization internal data. public void Deserialize_HandlesDeserializationUsingUserOption_WhenUsingInternalOptionThrewException() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, Binder = new CustomSerializerBinder(), DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, }); var json = "{\"$type\":\"HANGFIRE.CORE.TESTS.COMMON.SERIALIZATIONHELPERFACTS+CLASSB, mscorlib\",\"StringValue\":\"B\",\"DateTimeValue\":\"\\/Date(1356044400000)\\/\"}"; var value = SerializationHelper.Deserialize(json, typeof(ClassB)); var obj = value as ClassB; Assert.NotNull(obj); Assert.Equal("B", obj.StringValue); Assert.Equal(new DateTime(2012, 12, 20, 23, 00, 00, DateTimeKind.Utc), obj.DateTimeValue); } [DataCompatibilityRangeFact, CleanSerializerSettings] //This test is here to check backward compatibility. Earlier user settings is used for serialization internal data. public void Deserialize_RethrowsAnException_WhenUsingInternalOptionThrewException_AndUserSettingsAttemptFailedToo() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, DateFormatString = "ddMMyyyy" }); var json = "{\"$type\":\"HANGFIRE.CORE.TESTS.COMMON.SERIALIZATIONHELPERFACTS+CLASSB, someAssembly\",\"StringValue\":\"B\",\"DateTimeValue\":\"12041961\"}"; Assert.Throws(() => SerializationHelper.Deserialize(json, typeof(ClassB))); } [DataCompatibilityRangeFact] public void DeserializeGeneric_ReturnsNull_WhenValueIsNull() { var result = SerializationHelper.Deserialize(null); Assert.Null(result); } [DataCompatibilityRangeFact] public void DeserializeGeneric_ReturnsDefaultValue_WhenGenericArgumentIsValueType() { var result = SerializationHelper.Deserialize(null); Assert.Equal(0, result); } [DataCompatibilityRangeFact] public void DeserializeGeneric_ReturnsCorrectValue_WhenValueIsString() { var result = SerializationHelper.Deserialize("\"hello\""); Assert.Equal("hello", result); } [DataCompatibilityRangeFact] public void DeserializeGeneric_ReturnsCorrectObject_WhenTypeIsCustomClass() { var valueJson = @"{""PropertyA"":""A""}"; var value = SerializationHelper.Deserialize(valueJson); Assert.NotNull(value); Assert.Equal("A", value.PropertyA); } [DataCompatibilityRangeFact] public void DeserializeGeneric_ReturnsCorrectObject_WhenOptionsIsTypedInternal() { var valueJson = @"{""PropertyA"":""A""}"; var value = SerializationHelper.Deserialize(valueJson, SerializationOption.TypedInternal); Assert.NotNull(value); Assert.Equal("A", value.PropertyA); } [DataCompatibilityRangeFact, CleanSerializerSettings] //This test is here to check backward compatibility. Earlier user settings is used for serialization internal data. public void DeserializeGeneric_HandlesUsingUserOption_WhenUsingTypedInternalOptionThrewException() { SerializationHelper.SetUserSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, }); var valueJson = SerializationHelper.Serialize(new ClassA("A"), SerializationOption.User); var value = SerializationHelper.Deserialize(valueJson, SerializationOption.TypedInternal); Assert.NotNull(value); Assert.Equal("A", value.PropertyA); } [DataCompatibilityRangeFact, CleanSerializerSettings] public void DeserializeGeneric_RethrowsJsonException_WhenValueHasIncorrectFormat() { var valueJson = "asdfaljsadkfh"; Assert.Throws(() => SerializationHelper.Deserialize(valueJson)); } [DataCompatibilityRangeFact] public void GetProtectedSettings_SetsDefaultSettings() { var serializerSettings = SerializationHelper.GetInternalSettings(); Assert.Equal(TypeNameHandling.Auto, serializerSettings.TypeNameHandling); #if !NET452 && !NET461 && !NETCOREAPP1_0 Assert.Equal(TypeNameAssemblyFormatHandling.Simple, serializerSettings.TypeNameAssemblyFormatHandling); #endif Assert.Equal(DefaultValueHandling.IgnoreAndPopulate, serializerSettings.DefaultValueHandling); Assert.Equal(NullValueHandling.Ignore, serializerSettings.NullValueHandling); Assert.True(serializerSettings.CheckAdditionalContent); } private interface IClass { } private class ClassA : IClass { public ClassA(string propertyA) { PropertyA = propertyA; } public string PropertyA { get; } } private class ClassB { // ReSharper disable once UnusedAutoPropertyAccessor.Local public string StringValue { get; set; } // ReSharper disable once UnusedMember.Local public object NullValue { get; set; } // ReSharper disable once UnusedMember.Local public int DefaultValue { get; set; } // ReSharper disable once UnusedAutoPropertyAccessor.Local public DateTime? DateTimeValue { get; set; } } #pragma warning disable 618 private class CustomSerializerBinder : SerializationBinder #pragma warning restore 618 { public override void BindToName(Type serializedType, out string assemblyName, out string typeName) { assemblyName = "someAssembly"; typeName = serializedType.FullName.ToUpper(); } public override Type BindToType(string assemblyName, string typeName) { if (typeName.Contains("ClassA")) return typeof(ClassA); return typeof(ClassB); } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/ShallowExceptionHelperFacts.cs ================================================ using System; using Hangfire.Common; using Xunit; namespace Hangfire.Core.Tests.Common { public class ShallowExceptionHelperFacts { [Fact] public void PreserveOriginalStackTrace_CanBeCalledTwice_WithoutThrowingAnyException() { try { throw new InvalidOperationException("Hello, world!"); } catch (Exception ex) { ex.PreserveOriginalStackTrace(); ex.PreserveOriginalStackTrace(); } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Common/TypeExtensionsFacts.cs ================================================ using System; using System.Collections.Generic; using Hangfire.Common; using Xunit; // ReSharper disable InvokeAsExtensionMethod // ReSharper disable AssignNullToNotNullAttribute // ReSharper disable UnusedTypeParameter public class ClassWithoutNamespace { } namespace Hangfire.Core.Tests.Common { public class TypeExtensionsFacts { [Fact] public void ToGenericTypeString_PrintsNonGenericNestedClassName_WithDot() { Assert.Equal("NonGenericClass", typeof(NonGenericClass).ToGenericTypeString()); Assert.Equal("NonGenericClass.NestedNonGenericClass", typeof(NonGenericClass.NestedNonGenericClass).ToGenericTypeString()); Assert.Equal("NonGenericClass.NestedNonGenericClass.DoubleNestedNonGenericClass", typeof(NonGenericClass.NestedNonGenericClass.DoubleNestedNonGenericClass).ToGenericTypeString()); } [Fact] public void ToGenericTypeString_PrintsOpenGenericNestedClassName_WithGenericParameters() { Assert.Equal("NonGenericClass.NestedGenericClass", typeof(NonGenericClass.NestedGenericClass<,>).ToGenericTypeString()); Assert.Equal("GenericClass", typeof(GenericClass<>).ToGenericTypeString()); Assert.Equal("GenericClass.NestedNonGenericClass", typeof(GenericClass<>.NestedNonGenericClass).ToGenericTypeString()); Assert.Equal("GenericClass.NestedNonGenericClass.DoubleNestedGenericClass", typeof(GenericClass<>.NestedNonGenericClass.DoubleNestedGenericClass<,,>).ToGenericTypeString()); } [Fact] public void ToGenericTypeString_PrintsClosedGenericNestedClassName_WithGivenTypes() { Assert.Equal("NonGenericClass.NestedGenericClass>", typeof(NonGenericClass.NestedGenericClass>).ToGenericTypeString()); Assert.Equal("GenericClass", typeof(GenericClass).ToGenericTypeString()); Assert.Equal("GenericClass>.NestedNonGenericClass", typeof(GenericClass>.NestedNonGenericClass).ToGenericTypeString()); Assert.Equal("GenericClass>.NestedNonGenericClass.DoubleNestedGenericClass,Stack>>>.NestedNonGenericClass.DoubleNestedGenericClass,Stack>", typeof(GenericClass>.NestedNonGenericClass.DoubleNestedGenericClass, Stack>>>.NestedNonGenericClass.DoubleNestedGenericClass, Stack>).ToGenericTypeString()); } [Fact] public void ToGenericTypeString_CorrectlyHandlesTypesWithoutNamespace() { Assert.Equal("ClassWithoutNamespace", typeof(ClassWithoutNamespace).ToGenericTypeString()); } [Fact] public void GetNonOpenMatchingMethod_ThrowsAnException_WhenTypeIsNull() { var exception = Assert.Throws( () => TypeExtensions.GetNonOpenMatchingMethod(null, "Method", new Type[0])); Assert.Equal("type", exception.ParamName); } [Fact] public void GetNonOpenMatchingMethod_ThrowsAnException_WhenNameIsNull() { var exception = Assert.Throws( () => TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), null, new Type[0])); Assert.Equal("name", exception.ParamName); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectMethod() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "Method", new Type[0]); Assert.Equal("Method", method.Name); Assert.Equal(typeof(NonGenericClass), method.DeclaringType); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectMethodWithNoParameter_WhenParameterTypesIsNull() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "Method", null); Assert.Equal("Method", method.Name); Assert.Equal(typeof(NonGenericClass), method.DeclaringType); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectMethodWithOneParameter() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "Method", new[] { typeof(int) }); Assert.Equal("Method", method.Name); Assert.Equal(typeof(NonGenericClass), method.DeclaringType); Assert.Single(method.GetParameters()); Assert.Equal(typeof(int), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectMethodWithManyParameters() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "Method", new[] { typeof(int), typeof(int) }); Assert.Equal("Method", method.Name); Assert.Equal(typeof(NonGenericClass), method.DeclaringType); Assert.Equal(2, method.GetParameters().Length); Assert.Equal(typeof(int), method.GetParameters()[0].ParameterType); Assert.Equal(typeof(int), method.GetParameters()[1].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectMethod_WhenTypeIsInterface() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(IParent), "Method", new Type[0]); Assert.Equal("Method", method.Name); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodDefinedInBaseInterface() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(IChild), "Method", new Type[0]); Assert.Equal("Method", method.Name); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectGenericMethod() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "TrivialGenericMethod", new[] { typeof(int), typeof(string), typeof(object) }); Assert.Equal("TrivialGenericMethod", method.Name); Assert.Equal(typeof(NonGenericClass), method.DeclaringType); Assert.True(method.IsGenericMethod); Assert.False(method.ContainsGenericParameters); } [Fact] public void GetNonOpenMatchingMethod_ReturnsNull_WhenMethodCouldNotBeFound() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "NonExistingMethod", new Type[0]); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_ReturnsNull_WhenOveroladedMethodCouldNotBeFound() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "Method", new[] { typeof(object), typeof(int) }); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodNameIsCaseSensitive() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "method", new Type[0]); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_ReturnsNull_WhenMethodParameterTypeIsAssignableFromPassedType() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "Method", new[] { typeof(NonGenericClass) }); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasParameterWhoseTypeContainsGenericParameter() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "OtherGenericMethod", new[] { typeof(IEnumerable) }); Assert.Equal("OtherGenericMethod", method.Name); Assert.Single(method.GetParameters()); Assert.Equal(typeof(IEnumerable), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasParameterWhoseTypeContainsGenericParameterAndIsComplicated() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "OtherGenericMethod", new[] { typeof(List>) }); Assert.Equal("OtherGenericMethod", method.Name); Assert.Single(method.GetParameters()); Assert.Equal(typeof(List>), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasParameterWhoseTypeIsGenericAndContainsTwoGenericParameters() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "OtherGenericMethod", new[] { typeof(Tuple) }); Assert.Equal("OtherGenericMethod", method.Name); Assert.Single(method.GetParameters()); Assert.Equal(typeof(Tuple), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_HandlesNonTrivialOrderOfUsingMethodGenericParametersInMethodParameterTypes() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "OneMoreGenericMethod", new[] { typeof(Tuple) }); Assert.Equal("OneMoreGenericMethod", method.Name); Assert.Single(method.GetParameters()); Assert.Equal(typeof(Tuple), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasSomeParametersOfTheSameTypeWhichIsMethodGenericParameter() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new[] { typeof(int), typeof(int) }); Assert.Equal("GenericMethod", method.Name); Assert.Equal(2, method.GetParameters().Length); Assert.Equal(typeof(int), method.GetParameters()[0].ParameterType); Assert.Equal(typeof(int), method.GetParameters()[1].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasGenericAndNonGenericParameters() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new[] { typeof(int), typeof(NonGenericClass), typeof(double) }); Assert.Equal("GenericMethod", method.Name); Assert.Equal(3, method.GetParameters().Length); Assert.Equal(typeof(int), method.GetParameters()[0].ParameterType); Assert.Equal(typeof(NonGenericClass), method.GetParameters()[1].ParameterType); Assert.Equal(typeof(double), method.GetParameters()[2].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasParameterOfGenericTypeWhichContainsMe() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new[] { typeof(Tuple>) }); Assert.Equal("GenericMethod", method.Name); Assert.Single(method.GetParameters()); Assert.Equal(typeof(Tuple>), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasSomeParametersWhoseTypesContainsTheSameGenericParameter() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new[] { typeof(int), typeof(double) }); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_ReturnsNull_WhenParameterTypeIsMatchedByGenericTypeAndNotMatchedByGenericArguments() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "OtherGenericMethod", new[] { typeof(List)}); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectMethod_WhenParameterTypeIsGenericArray() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new[] { typeof(string[]) }); Assert.Equal("GenericMethod", method.Name); Assert.Equal(typeof(string[]), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_ReturnsCorrectMethod_WhenParameterTypeIsComplicatedGenericArray() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new[] { typeof(List[]) }); Assert.Equal("GenericMethod", method.Name); Assert.Equal(typeof(List[]), method.GetParameters()[0].ParameterType); } [Fact] public void GetNonOpenMatchingMethod_ReturnsNull_WhenMatchingGenricMethodNotBeFound() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new[] { typeof(Tuple, string>) }); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasNoParametersOrTypes() { var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new Type[0]); Assert.Null(method); } [Fact] public void GetNonOpenMatchingMethod_HandlesMethodHasNoParametersOrTypes2() { // public void GenericMethod(T0 arg) { } var method = TypeExtensions.GetNonOpenMatchingMethod(typeof(NonGenericClass), "GenericMethod", new [] { typeof(string) }); Assert.Null(method); } } public class GenericClass { public class NestedNonGenericClass { public class DoubleNestedGenericClass { } } } public interface IParent { void Method(); } public interface IChild : IParent { } public class NonGenericClass { public void Method() { } public void Method(int arg) { } public void Method(int arg0, int arg1) { } public void Method(IParent arg) { } public void Method(object arg) { } public void TrivialGenericMethod(T0 arg0, T1 arg1, T2 arg2) { } public void OtherGenericMethod(IEnumerable arg0) { } public void OtherGenericMethod(List> arg0) { } public void OtherGenericMethod(Tuple arg0) { } public void OneMoreGenericMethod(Tuple arg0) { } public void GenericMethod() { } public void GenericMethod(T0 arg) { } public void GenericMethod(T arg0, T arg1) { } public void GenericMethod(int arg0, T arg1, double arg2) { } public void GenericMethod(Tuple> arg) { } public void GenericMethod(T[] arg) { } public void GenericMethod(List[] arg) { } public class NestedNonGenericClass { public class DoubleNestedNonGenericClass { } } public class NestedGenericClass { } } } ================================================ FILE: tests/Hangfire.Core.Tests/ContinuationsSupportAttributeFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; using static Hangfire.ContinuationsSupportAttribute; namespace Hangfire.Core.Tests { public class ContinuationsSupportAttributeFacts { private const string _parentId = "parent-id"; private const string _continuationId = "continuation-id"; private readonly ElectStateContextMock _context; private readonly Mock _nextState; private readonly Mock _stateChanger; public ContinuationsSupportAttributeFacts() { _context = new ElectStateContextMock(); _nextState = new Mock(); _nextState.SetupGet(x => x.Name).Returns("SomeState"); _stateChanger = new Mock(); _context.ApplyContext.Connection.Setup(x => x.GetStateData(_parentId)).Returns(new StateData()); _context.ApplyContext.Connection.Setup(x => x.GetJobData(_parentId)).Returns(new JobData()); _context.ApplyContext.NewStateObject = new AwaitingState(_parentId, _nextState.Object); _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _continuationId }; } [Fact] public void OnStateElection_AddsAContinuationForParentJob_IfCandidateStateIsAwaiting() { // Arrange var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Assert _context.ApplyContext.Connection.Verify(x => x.AcquireDistributedLock("job:parent-id:state-lock", It.IsAny())); _context.ApplyContext.Connection.Verify(x => x.SetJobParameter( _parentId, "Continuations", It.Is(value => SerializationHelper.Deserialize>(value) .Contains(new Continuation { JobId = _continuationId, Options = JobContinuationOptions.OnAnyFinishedState})))); Assert.IsType(_context.Object.CandidateState); } [Fact] public void OnStateElection_ChangesCandidateToTheNextState_OnAwaitingCompletedParentJob() { _context.ApplyContext.Connection.Setup(x => x.GetStateData(_parentId)).Returns(new StateData { Name = SucceededState.StateName }); var filter = CreateFilter(); filter.OnStateElection(_context.Object); Assert.Equal(_nextState.Object.Name, _context.Object.CandidateState.Name); } [Fact] public void OnStateElection_AddsAnotherContinuationForParentJob_OnAwaitingState() { // Arrange _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = "another-id" } })); var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Assert var expectedList = JsonConvert.SerializeObject(new List { new Continuation { JobId = "another-id", Options = JobContinuationOptions.OnAnyFinishedState }, new Continuation { JobId = _continuationId, Options = JobContinuationOptions.OnAnyFinishedState } }); _context.ApplyContext.Connection.Verify(x => x.SetJobParameter(_parentId, "Continuations", expectedList)); } [Fact] public void OnStateElection_ThrowsAnException_WhenParentJobDoesNotExist() { _context.ApplyContext.Connection.Setup(x => x.GetJobData(_parentId)).Returns((JobData)null); var filter = CreateFilter(); Assert.Throws(() => filter.OnStateElection(_context.Object)); } [Fact] public void OnStateElection_ExecuteContinuations_IfExist() { // Arrange _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _parentId }; _context.ApplyContext.NewStateObject = new SucceededState(null, 0, 0); _context.ApplyContext.Connection.Setup(x => x.GetJobData(_continuationId)).Returns(new JobData()); _context.ApplyContext.Connection.Setup(x => x.GetStateData(_continuationId)).Returns(new StateData { Name = AwaitingState.StateName, Data = new Dictionary { { "NextState", SerializationHelper.Serialize(new EnqueuedState(), SerializationOption.TypedInternal) } } }); _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = _continuationId } })); var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is( ctx => ctx.BackgroundJobId == _continuationId && ctx.ExpectedStates.SingleOrDefault(s => s == AwaitingState.StateName) != null && ctx.NewState.Name == "Enqueued"))); } [Fact] public void OnStateElection_ExecuteContinuations_InFailedState_OnNextStateDeserializationError() { // Arrange _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _parentId }; _context.ApplyContext.NewStateObject = new SucceededState(null, 0, 0); _context.ApplyContext.Connection.Setup(x => x.GetJobData(_continuationId)).Returns(new JobData()); _context.ApplyContext.Connection.Setup(x => x.GetStateData(_continuationId)).Returns(new StateData { Name = AwaitingState.StateName, Data = new Dictionary { { "NextState", "hello" } } }); _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = _continuationId } })); var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == _continuationId && ctx.ExpectedStates.SingleOrDefault(s => s == AwaitingState.StateName) != null && ctx.NewState.Name == FailedState.StateName))); } [Fact] public void OnStateElection_ExecuteContinuations_InFailedState_WhenNextStateDoesNotExist() { // Arrange _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _parentId }; _context.ApplyContext.NewStateObject = new SucceededState(null, 0, 0); _context.ApplyContext.Connection.Setup(x => x.GetJobData(_continuationId)).Returns(new JobData()); _context.ApplyContext.Connection.Setup(x => x.GetStateData(_continuationId)).Returns(new StateData { Name = AwaitingState.StateName }); _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = _continuationId } })); var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == _continuationId && ctx.ExpectedStates.SingleOrDefault(s => s == AwaitingState.StateName) != null && ctx.NewState.Name == FailedState.StateName))); } [Fact] public void OnStateElection_SkipsContinuations_WhenTheirCurrentState_IsNotAwaiting() { // Arrange _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _parentId }; _context.ApplyContext.NewStateObject = new SucceededState(null, 0, 0); _context.ApplyContext.Connection.Setup(x => x.GetJobData(_continuationId)).Returns(new JobData()); _context.ApplyContext.Connection.Setup(x => x.GetStateData(_continuationId)).Returns(new StateData()); _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = _continuationId } })); var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Never); Assert.Equal("Succeeded", _context.Object.CandidateState.Name); } [Fact] public void OnStateElection_SkipsExpiredContinuations() { // Arrange _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _parentId }; _context.ApplyContext.NewStateObject = new SucceededState(null, 0, 0); _context.ApplyContext.Connection.Setup(x => x.GetJobData(_continuationId)).Returns((JobData)null); _context.ApplyContext.Connection.Setup(x => x.GetStateData(_continuationId)).Returns((StateData)null); _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = _continuationId } })); var filter = CreateFilter(); // Act & Assert filter.OnStateElection(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Never); Assert.Equal("Succeeded", _context.Object.CandidateState.Name); } [Fact(Timeout = 20 * 1000)] public void OnStateElection_DoesNotStuckForever_WhenContinuationHasNoCorrespondingStateEntry() { // Arrange _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _parentId }; _context.ApplyContext.NewStateObject = new SucceededState(null, 0, 0); _context.ApplyContext.Connection.Setup(x => x.GetJobData(_continuationId)).Returns(new JobData()); _context.ApplyContext.Connection.Setup(x => x.GetStateData(_continuationId)).Returns((StateData)null); _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = _continuationId } })); var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Asser _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Never); Assert.Equal("Succeeded", _context.Object.CandidateState.Name); } [Fact] public void OnStateElection_SkipsContinuations_WithNullIds() { // Arrange _context.ApplyContext.BackgroundJob = new BackgroundJobMock { Id = _parentId }; _context.ApplyContext.NewStateObject = new SucceededState(null, 0, 0); _context.ApplyContext.Connection.Setup(x => x.GetStateData(null)).Throws(); _context.ApplyContext.Connection.Setup(x => x.GetJobParameter(_parentId, "Continuations")) .Returns(SerializationHelper.Serialize(new List { new Continuation { JobId = null } })); var filter = CreateFilter(); // Act filter.OnStateElection(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Never); Assert.Equal("Succeeded", _context.Object.CandidateState.Name); } [Fact] public void OnStateUnapplied_DoesNotThrow() { var filter = (IApplyStateFilter)CreateFilter(); // Does not throw filter.OnStateUnapplied(null, null); } [DataCompatibilityRangeFact, CleanSerializerSettings] public void HandlesChangingProcessOfInternalDataSerialization() { SerializationHelper.SetUserSerializerSettings(SerializerSettingsHelper.DangerousSettings); var continuationsJson = SerializationHelper.Serialize(new List { new Continuation {JobId = "1", Options = JobContinuationOptions.OnAnyFinishedState}, new Continuation {JobId = "3214324", Options = JobContinuationOptions.OnlyOnSucceededState} }, SerializationOption.User); var continuations = SerializationHelper.Deserialize>(continuationsJson); Assert.NotNull(continuations); Assert.Equal(2, continuations.Count); Assert.Equal("1", continuations[0].JobId); Assert.Equal(JobContinuationOptions.OnAnyFinishedState, continuations[0].Options); Assert.Equal("3214324", continuations[1].JobId); Assert.Equal(JobContinuationOptions.OnlyOnSucceededState, continuations[1].Options); } // https://github.com/HangfireIO/Hangfire/issues/1470 [DataCompatibilityRangeFact, CleanSerializerSettings] public void DeserializeContinuations_CanHandleFieldBasedSerialization_OfContinuationClass() { #pragma warning disable 618 JobHelper.SetSerializerSettings(new JsonSerializerSettings { ContractResolver = new FieldsOnlyContractResolver() }); #pragma warning restore 618 var payload = "[{\"k__BackingField\":\"123\",\"k__BackingField\":1},{\"k__BackingField\":\"456\",\"k__BackingField\":0}]"; var data = DeserializeContinuations(payload); Assert.Equal("123", data[0].JobId); Assert.Equal(JobContinuationOptions.OnlyOnSucceededState, data[0].Options); Assert.Equal("456", data[1].JobId); Assert.Equal(JobContinuationOptions.OnAnyFinishedState, data[1].Options); } private class FieldsOnlyContractResolver: DefaultContractResolver { protected override List GetSerializableMembers(Type objectType) => objectType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Cast() .ToList(); protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) => base.CreateProperties(type, MemberSerialization.Fields); } private ContinuationsSupportAttribute CreateFilter() { return new ContinuationsSupportAttribute( pushResults: false, ContinuationsSupportAttribute.KnownFinalStates, _stateChanger.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/CronFacts.cs ================================================ using System; using Xunit; namespace Hangfire.Core.Tests { public class CronFacts { [Fact] public void Minutely_ReturnsFormattedString() { string expected = "* * * * *"; string actual = Cron.Minutely(); Assert.Equal(expected, actual); } [Fact] public void Hourly_WithoutMinute_ReturnsFormattedStringWithDefaults() { string expected = "0 * * * *"; string actual = Cron.Hourly(); Assert.Equal(expected, actual); } [Fact] public void Hourly_WithMinute_ReturnsFormattedStringWithMinute() { string expected = "5 * * * *"; string actual = Cron.Hourly(5); Assert.Equal(expected, actual); } [Fact] public void Daily_WithoutMinuteOrHour_ReturnsFormattedStringWithDefaults() { string expected = "0 0 * * *"; string actual = Cron.Daily(); Assert.Equal(expected, actual); } [Fact] public void Daily_WithoutMinute_ReturnsFormattedStringWithHourAndZeroMinute() { string expected = "0 5 * * *"; string actual = Cron.Daily(5); Assert.Equal(expected, actual); } [Fact] public void Daily_WithMinuteAndHour_ReturnsFormattedStringWithHourAndMinute() { string expected = "5 5 * * *"; string actual = Cron.Daily(5, 5); Assert.Equal(expected, actual); } [Fact] public void Weekly_WithoutDayHourMinute_ReturnsFormattedStringWithDefaults() { string expected = "0 0 * * " + ((int)DayOfWeek.Monday).ToString(); string actual = Cron.Weekly(); Assert.Equal(expected, actual); } [Fact] public void Weekly_WithDayWithoutHourMinute_ReturnsFormattedStringWithDay() { DayOfWeek day = DayOfWeek.Thursday; string expected = "0 0 * * " + ((int)day).ToString(); string actual = Cron.Weekly(day); Assert.Equal(expected, actual); } [Fact] public void Weekly_WithDayHourWithoutMinute_ReturnsFormattedStringWithDayHour() { DayOfWeek day = DayOfWeek.Thursday; int hour = 5; string expected = "0 " + hour.ToString() + " * * " + ((int)day).ToString(); string actual = Cron.Weekly(day, hour); Assert.Equal(expected, actual); } [Fact] public void Weekly_WithDayHourMinute_ReturnsFormattedStringWithDayHourMinute() { DayOfWeek day = DayOfWeek.Thursday; int hour = 5; int minute = 5; string expected = minute.ToString() + " " + hour.ToString() + " * * " + ((int)day).ToString(); string actual = Cron.Weekly(day, hour, minute); Assert.Equal(expected, actual); } [Fact] public void Monthly_WithoutDayHourMinute_ReturnsFormattedStringWithDefaults() { string expected = "0 0 1 * *"; string actual = Cron.Monthly(); Assert.Equal(expected, actual); } [Fact] public void Monthly_WithoutHourMinuteWithDay_ReturnsFormattedStringWithDay() { int day = 6; string expected = "0 0 " + day.ToString() + " * *"; string actual = Cron.Monthly(day); Assert.Equal(expected, actual); } [Fact] public void Monthly_WithoutMinuteWithDayHour_ReturnsFormattedStringWithDayHour() { int day = 7; int hour = 4; string expected = "0 " + hour.ToString() + " " + day.ToString() + " * *"; string actual = Cron.Monthly(day, hour); Assert.Equal(expected, actual); } [Fact] public void Monthly_WithDayHourMinute_ReturnsFormattedStringWithDayHourMinute() { int day = 7; int hour = 4; int minute = 23; string expected = minute.ToString() + " " + hour.ToString() + " " + day.ToString() + " * *"; string actual = Cron.Monthly(day, hour, minute); Assert.Equal(expected, actual); } [Fact] public void Yearly_WithoutMonthDayHourMinute_ReturnsFormattedStringWithDefaults() { string expected = "0 0 1 1 *"; string actual = Cron.Yearly(); Assert.Equal(expected, actual); } [Fact] public void Yearly_WithoutDayHourMinuteWithMonth_ReturnsFormattedStringWithMonth() { int month = 7; string expected = "0 0 1 " + month.ToString() + " *"; string actual = Cron.Yearly(month); Assert.Equal(expected, actual); } [Fact] public void Yearly_WithoutHourMinuteWithMonthDay_ReturnsFormattedStringWithMonthDay() { int month = 8; int day = 18; string expected = "0 0 " + day.ToString() + " " + month.ToString() + " *"; string actual = Cron.Yearly(month, day); Assert.Equal(expected, actual); } [Fact] public void Yearly_WithoutMinuteWithMonthDayHour_ReturnsFormattedStringWithMonthDayHour() { int month = 3; int day = 18; int hour = 14; string expected = "0 " + hour.ToString() + " " + day.ToString() + " " + month.ToString() + " *"; string actual = Cron.Yearly(month, day, hour); Assert.Equal(expected, actual); } [Fact] public void Yearly_WithMonthDayHourMinute_ReturnsFormattedStringWithMonthDayHourMinute() { int month = 4; int day = 17; int hour = 3; int minute = 45; string expected = minute.ToString() + " " + hour.ToString() + " " + day.ToString() + " " + month.ToString() + " *"; string actual = Cron.Yearly(month, day, hour, minute); Assert.Equal(expected, actual); } [Fact] public void Never_ReturnsFormattedString() { string expected = "0 0 31 2 *"; string actual = Cron.Yearly(2, 31); Assert.Equal(expected, actual); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Dashboard/BatchCommandDispatcherFacts.cs ================================================ using Hangfire.Core.Tests.Stubs; using Hangfire.Dashboard; using Xunit; namespace Hangfire.Core.Tests.Dashboard { public class BatchCommandDispatcherFacts { [Fact] public void Dispatch_Sets401StatusCode_WhenNotPermitted() { var options = new DashboardOptions { IsReadOnlyFunc = _ => true }; var context = new DashboardContextStub(options); var dispatcher = new BatchCommandDispatcher((DashboardContext ctx, string str) => { }); dispatcher.Dispatch(context); Assert.Equal(401, context.Response.StatusCode); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Dashboard/CommandDispatcherFacts.cs ================================================ using Hangfire.Core.Tests.Stubs; using Hangfire.Dashboard; using Xunit; namespace Hangfire.Core.Tests.Dashboard { public class CommandDispatcherFacts { [Fact] public void Dispatch_Sets401StatusCode_WhenNotPermitted() { var options = new DashboardOptions { IsReadOnlyFunc = _ => true }; var context = new DashboardContextStub(options); var dispatcher = new CommandDispatcher((DashboardContext _) => false); dispatcher.Dispatch(context); Assert.Equal(401, context.Response.StatusCode); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Dashboard/DashboardOptionsFacts.cs ================================================ using System.Linq; using Hangfire.Dashboard; using Xunit; namespace Hangfire.Core.Tests.Dashboard { public class DashboardOptionsFacts { [Fact] public void Ctor_SetsDefaultValues_ForAllOptions() { var options = new DashboardOptions(); Assert.Equal("/", options.AppPath); Assert.Equal("", options.PrefixPath); Assert.NotNull(options.Authorization); Assert.IsType(options.Authorization.FirstOrDefault()); Assert.Equal(2000, options.StatsPollingInterval); Assert.True(options.DisplayStorageConnectionString); Assert.Equal("Hangfire Dashboard", options.DashboardTitle); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Dashboard/HtmlHelperFacts.cs ================================================ using System; using Hangfire.Dashboard; using Moq; using Xunit; namespace Hangfire.Core.Tests.Dashboard { public class HtmlHelperFacts { private readonly Mock _page; public HtmlHelperFacts() { _page = new Mock(); } [Fact] public void ToHumanDuration_FormatsFractionalSeconds() { var helper = CreateHelper(); var result = helper.ToHumanDuration(TimeSpan.FromSeconds(1.087)); Assert.Equal("+1.087s", result); } private HtmlHelper CreateHelper() { return new HtmlHelper(_page.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/GlobalConfigurationExtensionsFacts.cs ================================================ using Hangfire.Common; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; namespace Hangfire.Core.Tests { public class GloabalConfigurationExtensionsFacts { [Fact, CleanSerializerSettings] public void UseSerializationSettings_AffectSerializationWithUserSettings() { GlobalConfiguration.Configuration.UseSerializerSettings(new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); var result = SerializationHelper.Serialize(new CustomClass { StringProperty = "Value" }, SerializationOption.User); Assert.Equal(@"{""stringProperty"":""Value""}", result); } [Fact, CleanSerializerSettings] public void UseSerializationSettingsWithCallback_AffectSerializationWithUserSettings() { GlobalConfiguration.Configuration.UseRecommendedSerializerSettings(settings => { settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); }); var result = SerializationHelper.Serialize(new CustomClass { StringProperty = "Value" }, SerializationOption.User); Assert.Equal(@"{""stringProperty"":""Value""}", result); } public class CustomClass { public string StringProperty { get; set; } } } } ================================================ FILE: tests/Hangfire.Core.Tests/GlobalStateHandlersFacts.cs ================================================ using System.Linq; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests { public class GlobalStateHandlersFacts { [Fact] public void AllBasicHandlersShouldBeIncluded() { var handlerTypes = GlobalStateHandlers.Handlers.Select(x => x.GetType()).ToArray(); Assert.Contains(typeof(SucceededState.Handler), handlerTypes); Assert.Contains(typeof(ScheduledState.Handler), handlerTypes); Assert.Contains(typeof(EnqueuedState.Handler), handlerTypes); Assert.Contains(typeof(DeletedState.Handler), handlerTypes); } } } ================================================ FILE: tests/Hangfire.Core.Tests/GlobalTestsConfiguration.cs ================================================ using Xunit; [assembly: CollectionBehavior(MaxParallelThreads = 1)] ================================================ FILE: tests/Hangfire.Core.Tests/Hangfire.Core.Tests.csproj ================================================  net452;net461;net6.0;net8.0 0618 ReferencedCronos ================================================ FILE: tests/Hangfire.Core.Tests/Hangfire.Core.Tests.csproj.DotSettings ================================================  True ================================================ FILE: tests/Hangfire.Core.Tests/JobActivatorFacts.cs ================================================ using System; using Xunit; namespace Hangfire.Core.Tests { public class JobActivatorFacts { [Fact, GlobalLock] public void SetCurrent_ThrowsAnException_WhenValueIsNull() { Assert.Throws(() => JobActivator.Current = null); } [Fact, GlobalLock] public void GetCurrent_ReturnsPreviouslySetValue() { var activator = new JobActivator(); JobActivator.Current = activator; Assert.Same(activator, JobActivator.Current); } [Fact] public void DefaultActivator_CanCreateInstanceOfClassWithDefaultConstructor() { var activator = new JobActivator(); var instance = activator.ActivateJob(typeof (DefaultConstructor)); Assert.NotNull(instance); } [Fact] public void DefaultActivator_ThrowAnException_IfThereIsNoDefaultConstructor() { var activator = new JobActivator(); Assert.Throws( () => activator.ActivateJob(typeof (CustomConstructor))); } public class DefaultConstructor { } public class CustomConstructor { // ReSharper disable once UnusedParameter.Local public CustomConstructor(string arg) { } } } } ================================================ FILE: tests/Hangfire.Core.Tests/JobCancellationTokenFacts.cs ================================================ using System; using Xunit; namespace Hangfire.Core.Tests { public class JobCancellationTokenFacts { [Fact] public void ShutdownToken_IsInCanceledState_WhenPassingTrueValue() { var token = new JobCancellationToken(true); Assert.True(token.ShutdownToken.IsCancellationRequested); } [Fact] public void ThrowIfCancellationRequested_DoesNotThrowOnFalseValue() { var token = new JobCancellationToken(false); // Does not throw token.ThrowIfCancellationRequested(); } [Fact] public void ThrowIfCancellationRequested_ThrowsOnTrueValue() { var token = new JobCancellationToken(true); Assert.Throws( () => token.ThrowIfCancellationRequested()); } [Fact] public void Null_ReturnsNullValue() { Assert.Null(JobCancellationToken.Null); } } } ================================================ FILE: tests/Hangfire.Core.Tests/JobParameterInjectionFilterFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Common; using Hangfire.Server; using Moq; using Newtonsoft.Json; using Xunit; namespace Hangfire.Core.Tests { public class JobParameterInjectionFilterFacts { private readonly PerformContextMock _context; public JobParameterInjectionFilterFacts() { _context = new PerformContextMock(); } [Fact] public void OnPerforming_ThrowsArgumentNullException_WhenContextIsNull() { var filter = CreateFilter(); var exception = Assert.Throws(() => filter.OnPerforming(null)); Assert.Equal("context", exception.ParamName); } [Fact] public void OnPerforming_HandlesParameterlessMethods_WithoutDoingAnything() { _context.BackgroundJob.Job = Job.FromExpression(() => Parameterless()); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); _context.Connection.Verify(x => x.GetJobParameter(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void OnPerforming_DoesNotModifyNonDecoratedParameters() { _context.BackgroundJob.Job = Job.FromExpression(() => NonDecorated("hello", 1)); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); _context.Connection.Verify(x => x.GetJobParameter(It.IsAny(), It.IsAny()), Times.Never); Assert.Equal("hello", _context.BackgroundJob.Job.Args[0]); Assert.Equal(1, _context.BackgroundJob.Job.Args[1]); } [Fact] public void OnPerforming_ModifiesParameters_DecoratedWithASpecialAttribute_ThatHaveNullValue() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated(null)); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Result123")).Returns("\"Hello!\""); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal("Hello!", _context.BackgroundJob.Job.Args[0]); } [Fact] public void OnPerforming_RewritesValueTypeParameters_DecoratedWithASpecialAttribute_ThatHaveDefaultValue() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated(default(int))); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Result123")).Returns("12345"); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal(12345, _context.BackgroundJob.Job.Args[0]); } [Fact] public void OnPerforming_RewritesParameters_ThatDoNotHaveNullValue() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated("NonNull")); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Result123")).Returns("\"Hello!\""); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal("Hello!", _context.BackgroundJob.Job.Args[0]); } [Fact] public void OnPerforming_RewritesValueTypeParameters_DecoratedWithASpecialAttribute_ThatHaveNonDefaultValue() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated(123456)); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Result123")).Returns("54321"); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal(54321, _context.BackgroundJob.Job.Args[0]); } [Fact] public void OnPerforming_DoesNotRewriteDefaultValueWithNull_WhenJobParameterIsNull() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated(0)); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Result123")).Returns(null); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal(0, _context.BackgroundJob.Job.Args[0]); } [Fact] public void OnPerforming_DoesNotRewriteNonDefaultValueWithNull_WhenJobParameterIsNull() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated(123456)); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Result123")).Returns(null); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal(123456, _context.BackgroundJob.Job.Args[0]); } [Fact] public void OnPerforming_ThrowsDeserializationException_WhenParameterCanNotBeDeserialized() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated(null)); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Result123")).Returns("adk;hg"); var filter = CreateFilter(); Assert.ThrowsAny(() => filter.OnPerforming(_context.GetPerformingContext())); } [Fact] public void OnPerforming_ModifiesParameters_BasedOnFromResultAttribute() { _context.BackgroundJob.Job = Job.FromExpression(() => DecoratedWithFromResult(null)); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "AntecedentResult")).Returns("\"Result\""); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal("Result", _context.BackgroundJob.Job.Args[0]); } [Fact] public void OnPerforming_CanModifyMultipleParameters() { _context.BackgroundJob.Job = Job.FromExpression(() => Decorated(null, null)); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Param1")).Returns("\"Value1\""); _context.Connection.Setup(x => x.GetJobParameter(_context.BackgroundJob.Id, "Param2")).Returns("2"); var filter = CreateFilter(); filter.OnPerforming(_context.GetPerformingContext()); Assert.Equal("Value1", _context.BackgroundJob.Job.Args[0]); Assert.Equal(2, _context.BackgroundJob.Job.Args[1]); } [Fact] public void OnPerformed_DoesNotThrow_AnyException() { var filter = CreateFilter(); filter.OnPerformed(_context.GetPerformedContext()); } private JobParameterInjectionFilter CreateFilter() { return new JobParameterInjectionFilter(); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Parameterless() { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void NonDecorated(string arg1, int arg2) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Decorated([FromParameter("Result123")] string value) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Decorated([FromParameter("Result123")] int value) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void DecoratedWithFromResult([FromResult] string value) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Decorated([FromParameter("Param1")] string value1, [FromParameter("Param2")] int? value2) { } } } ================================================ FILE: tests/Hangfire.Core.Tests/JobStorageFacts.cs ================================================ using System; using Moq; using Xunit; namespace Hangfire.Core.Tests { public class JobStorageFacts { private readonly Mock _storage; public JobStorageFacts() { _storage = new Mock() { CallBase = true }; } [Fact, GlobalLock(Reason = "Access static JobStorage.Current member")] public void SetCurrent_DoesNotThrowAnException_WhenValueIsNull() { // Does not throw JobStorage.Current = null; } [Fact, GlobalLock(Reason = "Access static JobStorage.Current member")] public void GetCurrent_ThrowsAnException_OnUninitializedValue() { JobStorage.Current = null; Assert.Throws(() => JobStorage.Current); } [Fact, GlobalLock(Reason = "Access static JobStorage.Current member")] public void GetCurrent_ReturnsCurrentValue_WhenInitialized() { var storage = new Mock(); JobStorage.Current = storage.Object; Assert.Same(storage.Object, JobStorage.Current); } [Fact] public void GetComponents_ReturnsEmptyCollectionByDefault() { Assert.Empty(_storage.Object.GetComponents()); } [Fact] public void GetStateHandlers_ReturnsEmptyCollectionByDefault() { Assert.Empty(_storage.Object.GetStateHandlers()); } [Fact] public void JobExpirationTimeout_HasDefaultTimeoutFromDays1() { Assert.Equal(TimeSpan.FromDays(1), _storage.Object.JobExpirationTimeout); } [Fact] public void JobExpirationTimeout_CantAllowTimeoutLessThanOneHour() { var negative = TimeSpan.FromSeconds(-1); var exception = Assert.Throws(() => _storage.Object.JobExpirationTimeout = negative); Assert.StartsWith("JobStorage.JobExpirationTimeout value should be equal or greater than zero.", exception.Message); } [Fact] public void JobExpirationTimeout_CanChangeTheTimeout() { Assert.Equal(TimeSpan.FromDays(1), _storage.Object.JobExpirationTimeout); _storage.Object.JobExpirationTimeout = TimeSpan.FromHours(1); Assert.Equal(TimeSpan.FromHours(1), _storage.Object.JobExpirationTimeout); GlobalConfiguration.Configuration .UseStorage(_storage.Object) .WithJobExpirationTimeout(TimeSpan.FromDays(3)); Assert.Equal(TimeSpan.FromDays(3), _storage.Object.JobExpirationTimeout); } } } ================================================ FILE: tests/Hangfire.Core.Tests/LatencyTimeoutAttributeFacts.cs ================================================ using System; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests { public class LatencyTimeoutAttributeFacts { private const string JobId = "id"; private readonly ElectStateContextMock _context; public LatencyTimeoutAttributeFacts() { var state = new ProcessingState("Default", "1"); _context = new ElectStateContextMock(); _context.ApplyContext.BackgroundJob.Id = JobId; _context.ApplyContext.NewStateObject = state; } [Fact] public void Ctor_ThrowsAnException_WhenTimeoutInSecondsValueIsNegative() { var exception = Assert.Throws( () => CreateFilter(-1)); Assert.Equal("timeoutInSeconds", exception.ParamName); } [Fact] public void OnStateElection_ChangesToDeleted_IfTimeoutExceeded() { _context.ApplyContext.BackgroundJob.CreatedAt = DateTime.UtcNow.AddMinutes(-1); var filter = CreateFilter(10); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); } [Fact] public void OnStateElection_DoesNotChangeAnything_IfTimeoutNotExceeded() { _context.ApplyContext.BackgroundJob.CreatedAt = DateTime.UtcNow; var filter = CreateFilter(100); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); } private static LatencyTimeoutAttribute CreateFilter(int timeout) { return new LatencyTimeoutAttribute(timeout); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Mocks/ApplyStateContextMock.cs ================================================ using System; using System.Collections.Generic; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; using Moq; namespace Hangfire.Core.Tests { public class ApplyStateContextMock { private readonly Lazy _context; public ApplyStateContextMock() { Storage = new Mock(); Connection = new Mock(); Transaction = new Mock(); BackgroundJob = new BackgroundJobMock(); NewState = new Mock(); OldStateName = null; JobExpirationTimeout = TimeSpan.FromMinutes(1); StateMachine = new Mock(); _context = new Lazy( () => new ApplyStateContext( Storage.Object, Connection.Object, Transaction.Object, BackgroundJob.Object, NewStateObject ?? NewState.Object, OldStateName, EmptyProfiler.Instance, StateMachine.Object, CustomData) { JobExpirationTimeout = JobExpirationTimeout }); } public Mock Storage { get; set; } public Mock Connection { get; set; } public Mock Transaction { get; set; } public BackgroundJobMock BackgroundJob { get; set; } public IState NewStateObject { get; set; } public Mock NewState { get; set; } public string OldStateName { get; set; } public TimeSpan JobExpirationTimeout { get; set; } public Mock StateMachine { get; set; } public IReadOnlyDictionary CustomData { get; set; } public ApplyStateContext Object => _context.Value; } } ================================================ FILE: tests/Hangfire.Core.Tests/Mocks/BackgroundJobMock.cs ================================================ using System; using Hangfire.Common; namespace Hangfire.Core.Tests { public class BackgroundJobMock { private readonly Lazy _object; public BackgroundJobMock() { Id = "JobId"; Job = Job.FromExpression(() => SomeMethod()); CreatedAt = DateTime.UtcNow; _object = new Lazy( () => new BackgroundJob(Id, Job, CreatedAt)); } public string Id { get; set; } public Job Job { get; set; } public DateTime CreatedAt { get; set; } public BackgroundJob Object => _object.Value; public static void SomeMethod() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/Mocks/BackgroundProcessContextMock.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using Hangfire.Server; using Moq; namespace Hangfire.Core.Tests { public class BackgroundProcessContextMock { private readonly Lazy _context; public BackgroundProcessContextMock() { ServerId = "server"; Storage = new Mock(); Properties = new Dictionary(); ExecutionId = Guid.NewGuid(); StoppingTokenSource = new CancellationTokenSource(); StoppedTokenSource = new CancellationTokenSource(); ShutdownTokenSource = new CancellationTokenSource(); _context = new Lazy( () => new BackgroundProcessContext(ServerId, Storage.Object, Properties, ExecutionId, StoppingTokenSource.Token, StoppedTokenSource.Token, ShutdownTokenSource.Token)); } public BackgroundProcessContext Object => _context.Value; public string ServerId { get; set; } public Mock Storage { get; set; } public IDictionary Properties { get; set; } public Guid ExecutionId { get; set; } public CancellationTokenSource StoppingTokenSource { get; set; } public CancellationTokenSource StoppedTokenSource { get; set; } public CancellationTokenSource ShutdownTokenSource { get; set; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Mocks/CreateContextMock.cs ================================================ using System; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; namespace Hangfire.Core.Tests { public class CreateContextMock { private readonly Lazy _context; public CreateContextMock() { Storage = new Mock(); Connection = new Mock(); Job = Job.FromExpression(() => Method()); InitialState = new Mock(); _context = new Lazy( () => new CreateContext( Storage.Object, Connection.Object, Job, InitialState.Object)); } public Mock Storage { get; set; } public Mock Connection { get; set; } public Job Job { get; set; } public Mock InitialState { get; set; } public CreateContext Object => _context.Value; public static void Method() { } public CreatingContext GetCreatingContext() { return new CreatingContext(Object); } public CreatedContext GetCreatedContext( string jobId, DateTime? createdAt = null, bool canceled = false, Exception exception = null) { return new CreatedContext( Object, new BackgroundJob(jobId, Job, createdAt ?? DateTime.UtcNow), canceled, exception); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Mocks/ElectStateContextMock.cs ================================================ using System; using Hangfire.States; namespace Hangfire.Core.Tests { public class ElectStateContextMock { private readonly Lazy _context; public ElectStateContextMock() { ApplyContext = new ApplyStateContextMock(); _context = new Lazy( () => new ElectStateContext(ApplyContext.Object)); } public ApplyStateContextMock ApplyContext { get; set; } public ElectStateContext Object => _context.Value; } } ================================================ FILE: tests/Hangfire.Core.Tests/Mocks/PerformContextMock.cs ================================================ using System; using Hangfire.Server; using Hangfire.Storage; using Moq; namespace Hangfire.Core.Tests { public class PerformContextMock { private readonly Lazy _context; public PerformContextMock() { Storage = new Mock(); Connection = new Mock(); BackgroundJob = new BackgroundJobMock(); CancellationToken = new Mock(); _context = new Lazy( () => new PerformContext(Storage.Object, Connection.Object, BackgroundJob.Object, CancellationToken.Object)); } public Mock Storage { get; set; } public Mock Connection { get; set; } public BackgroundJobMock BackgroundJob { get; set; } public Mock CancellationToken { get; set; } public PerformContext Object => _context.Value; public static void SomeMethod() { } public PerformingContext GetPerformingContext() { return new PerformingContext(Object); } public PerformedContext GetPerformedContext(object result = null, bool canceled = false, Exception exception = null) { return new PerformedContext(Object, result, canceled, exception); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Mocks/StateChangeContextMock.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; using Moq; namespace Hangfire.Core.Tests { public class StateChangeContextMock { private readonly Lazy _context; public StateChangeContextMock() { Storage = new Mock(); Connection = new Mock(); BackgroundJobId = "JobId"; NewState = new Mock(); ExpectedStates = null; DisableFilters = false; CancellationToken = CancellationToken.None; _context = new Lazy( () => new StateChangeContext( Storage.Object, Connection.Object, Transaction?.Object, BackgroundJobId, NewState.Object, ExpectedStates, DisableFilters, CompleteJob?.Object, CancellationToken, EmptyProfiler.Instance, ServerId, CustomData)); } public Mock Storage { get; set; } public Mock Connection { get; set; } public Mock Transaction { get; set; } public Mock CompleteJob { get; set; } public string BackgroundJobId { get; set; } public Mock NewState { get; set; } public IEnumerable ExpectedStates { get; set; } public bool DisableFilters { get; set; } public CancellationToken CancellationToken { get; set; } public string ServerId { get; set; } public IReadOnlyDictionary CustomData { get; set; } public StateChangeContext Object => _context.Value; } } ================================================ FILE: tests/Hangfire.Core.Tests/Obsolete/ServerWatchdogOptionsFacts.cs ================================================ using System; using System.Threading; using Hangfire.Server; using Xunit; namespace Hangfire.Core.Tests.Obsolete { public class ServerWatchdogOptionsFacts { [Fact] public void Ctor_InitializeProperties_WithCorrectValues() { var options = CreateOptions(); Assert.Equal(TimeSpan.FromMinutes(5), options.CheckInterval); Assert.Equal(TimeSpan.FromMinutes(5), options.ServerTimeout); } [Fact] public void ServerTimeout_ThrowsAnException_WhenValueIsTooLarge() { var options = CreateOptions(); Assert.Throws( () => options.ServerTimeout = TimeSpan.FromHours(25)); } [Fact] public void ServerTimeout_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.ServerTimeout = TimeSpan.FromHours(-5)); } [Fact] public void ServerTimeout_ThrowsAnException_WhenValueIsInfinite() { var options = CreateOptions(); Assert.Throws( () => options.ServerTimeout = Timeout.InfiniteTimeSpan); } [Fact] public void CheckInterval_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.CheckInterval = TimeSpan.FromHours(-5)); } [Fact] public void CheckInterval_ThrowsAnException_WhenValueIsTooLarge() { var options = CreateOptions(); Assert.Throws( () => options.CheckInterval = TimeSpan.FromHours(25)); } [Fact] public void CheckInterval_ThrowsAnException_WhenValueIsInfinite() { var options = CreateOptions(); Assert.Throws( () => options.CheckInterval = Timeout.InfiniteTimeSpan); } [Fact] public void CheckInterval_DoesNotThrowException_WhenValueIsZero() { var options = CreateOptions(); Assert.Throws( () => options.CheckInterval = Timeout.InfiniteTimeSpan); } private static ServerWatchdogOptions CreateOptions() { return new ServerWatchdogOptions(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/PreserveCultureAttributeFacts.cs ================================================ using System; using System.Globalization; using Hangfire.Annotations; using Hangfire.Client; using Hangfire.Server; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests { public class PreserveCultureAttributeFacts { private readonly CreatingContext _creatingContext; private readonly PerformingContext _performingContext; private readonly PerformedContext _performedContext; private readonly Mock _connection; private const string JobId = "id"; public PreserveCultureAttributeFacts() { _connection = new Mock(); var storage = new Mock(); var backgroundJob = new BackgroundJobMock { Id = JobId }; var state = new Mock(); var createContext = new CreateContext( storage.Object, _connection.Object, backgroundJob.Job, state.Object); _creatingContext = new CreatingContext(createContext); var performContext = new PerformContext( _connection.Object, backgroundJob.Object, new Mock().Object); _performingContext = new PerformingContext(performContext); _performedContext = new PerformedContext(performContext, null, false, null); } [Fact] public void OnCreating_ThrowsAnException_WhenContextIsNull() { var filter = CreateFilter(); Assert.Throws( () => filter.OnCreating(null)); } [Fact] public void OnCreating_CapturesCultures_AndSetsThemAsJobParameters() { CultureHelper.SetCurrentCulture("ru-RU"); CultureHelper.SetCurrentUICulture("ru-RU"); var filter = CreateFilter(); filter.OnCreating(_creatingContext); Assert.Equal("ru-RU", _creatingContext.GetJobParameter("CurrentCulture")); Assert.Equal("ru-RU", _creatingContext.GetJobParameter("CurrentUICulture")); } [Fact] public void OnCreating_CapturesInvariantCulture_AndSetsStringEmptyAsJobParameters() { CultureHelper.SetCurrentCulture(CultureInfo.InvariantCulture); CultureHelper.SetCurrentUICulture(CultureInfo.InvariantCulture); var filter = CreateFilter(); filter.OnCreating(_creatingContext); Assert.Equal(String.Empty, _creatingContext.GetJobParameter("CurrentCulture")); Assert.Equal(String.Empty, _creatingContext.GetJobParameter("CurrentUICulture")); } [Fact] public void OnCreated_DoesNotThrowAnException() { var filter = CreateFilter(); // Does not throw filter.OnCreated(null); } [Fact] public void OnPerforming_SetsThreadCultures_ToTheSpecifiedOnesInJobParameters() { _connection.Setup(x => x.GetJobParameter(JobId, "CurrentCulture")).Returns("\"ru-RU\""); _connection.Setup(x => x.GetJobParameter(JobId, "CurrentUICulture")).Returns("\"ru-RU\""); CultureHelper.SetCurrentCulture("en-US"); CultureHelper.SetCurrentUICulture("en-US"); var filter = CreateFilter(); filter.OnPerforming(_performingContext); Assert.Equal("ru-RU", CultureInfo.CurrentCulture.Name); Assert.Equal("ru-RU", CultureInfo.CurrentUICulture.Name); } [Fact] public void OnPerforming_SetsInvariantThreadCultures_WhenJobParametersAreEmptyStrings() { _connection.Setup(x => x.GetJobParameter(JobId, "CurrentCulture")).Returns("\"\""); _connection.Setup(x => x.GetJobParameter(JobId, "CurrentUICulture")).Returns("\"\""); CultureHelper.SetCurrentCulture("en-US"); CultureHelper.SetCurrentUICulture("en-US"); var filter = CreateFilter(); filter.OnPerforming(_performingContext); Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentCulture); Assert.Equal(CultureInfo.InvariantCulture, CultureInfo.CurrentUICulture); } [Fact] public void OnPerforming_DoesNotDoAnything_WhenCultureJobParameterIsNotSet() { _connection.Setup(x => x.GetJobParameter(JobId, "CurrentCulture")).Returns((string)null); _connection.Setup(x => x.GetJobParameter(JobId, "CurrentUICulture")).Returns((string)null); CultureHelper.SetCurrentCulture("en-US"); CultureHelper.SetCurrentUICulture("en-US"); var filter = CreateFilter(); filter.OnPerforming(_performingContext); Assert.Equal("en-US", CultureInfo.CurrentCulture.Name); Assert.Equal("en-US", CultureInfo.CurrentUICulture.Name); } [Fact] public void OnPerformed_ThrowsAnException_WhenContextIsNull() { var filter = CreateFilter(); Assert.Throws(() => filter.OnPerformed(null)); } [Fact] public void OnPerformed_RestoresPreviousCurrentCulture() { _connection.Setup(x => x.GetJobParameter(JobId, "CurrentCulture")).Returns("\"ru-RU\""); _connection.Setup(x => x.GetJobParameter(JobId, "CurrentUICulture")).Returns("\"ru-RU\""); CultureHelper.SetCurrentCulture("en-US"); CultureHelper.SetCurrentUICulture("en-US"); var filter = CreateFilter(); filter.OnPerforming(_performingContext); filter.OnPerformed(_performedContext); Assert.Equal("en-US", CultureInfo.CurrentCulture.Name); Assert.Equal("en-US", CultureInfo.CurrentUICulture.Name); } [Fact] public void OnPerformed_RestoresPreviousCurrentCulture_OnlyIfItWasChanged() { _connection.Setup(x => x.GetJobParameter(JobId, "CurrentCulture")).Returns((string)null); _connection.Setup(x => x.GetJobParameter(JobId, "CurrentUICulture")).Returns((string)null); CultureHelper.SetCurrentCulture("en-US"); CultureHelper.SetCurrentUICulture("en-US"); var filter = CreateFilter(); filter.OnPerforming(_performingContext); filter.OnPerformed(_performedContext); Assert.Equal("en-US", CultureInfo.CurrentCulture.Name); Assert.Equal("en-US", CultureInfo.CurrentUICulture.Name); } private static CaptureCultureAttribute CreateFilter() { return new CaptureCultureAttribute(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Processing/TaskExtensionsFacts.cs ================================================ using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Xunit; using TaskExtensions = Hangfire.Processing.TaskExtensions; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Processing { public class TaskExtensionsFacts { private readonly ManualResetEvent _mre; private readonly CancellationTokenSource _cts; public TaskExtensionsFacts() { _mre = new ManualResetEvent(false); _cts = new CancellationTokenSource(); } [Fact] public async Task WaitOneAsync_ThrowsArgNullException_WhenWaitHandleIsNull() { var exception = await Assert.ThrowsAsync( async () => await TaskExtensions.WaitOneAsync(null, TimeSpan.Zero, CancellationToken.None)); Assert.Equal("waitHandle", exception.ParamName); } [Fact] public async Task WaitOneAsync_ThrowsOpCanceledException_WhenCancellationTokenIsCanceled() { _cts.Cancel(); var exception = await Assert.ThrowsAsync( async () => await TaskExtensions.WaitOneAsync(_mre, TimeSpan.Zero, _cts.Token)); Assert.Equal(_cts.Token, exception.CancellationToken); } [Fact] public async Task WaitOneAsync_ThrowsOpCanceledException_EvenWhenWaitHandleIsSignaled() { _cts.Cancel(); _mre.Set(); var exception = await Assert.ThrowsAsync( async () => await TaskExtensions.WaitOneAsync(_mre, Timeout.InfiniteTimeSpan, _cts.Token)); Assert.Equal(_cts.Token, exception.CancellationToken); } [Fact] public async Task WaitOneAsync_ReturnsTrue_WhenWaitHandleIsSignaled() { _mre.Set(); var result = await TaskExtensions.WaitOneAsync(_mre, Timeout.InfiniteTimeSpan, _cts.Token); Assert.True(result); } [Fact] public async Task WaitOneAsync_ReturnsTrue_WhenWaitHandleIsSignaled_AndTimeoutIsZero() { _mre.Set(); var result = await TaskExtensions.WaitOneAsync(_mre, TimeSpan.Zero, _cts.Token); Assert.True(result); } [Fact] public async Task WaitOneAsync_ReturnsFalseImmediately_WhenNotSignaled_AndTimeoutIsZero() { var result = await TaskExtensions.WaitOneAsync(_mre, TimeSpan.Zero, _cts.Token); Assert.False(result); } [Fact] public async Task WaitOneAsync_WaitsAndReturnsFalse_WhenNotSignaled_AndNonNullTimeout() { using (var mre = new ManualResetEvent(false)) { var sw = Stopwatch.StartNew(); var result = await TaskExtensions.WaitOneAsync(mre, TimeSpan.FromMilliseconds(500), CancellationToken.None); sw.Stop(); Assert.False(result, "result != false"); Assert.False(mre.WaitOne(TimeSpan.Zero), "mre is signaled"); Assert.True(sw.Elapsed > TimeSpan.FromMilliseconds(450), $"Elapsed: {sw.Elapsed.TotalMilliseconds} ms, Expected: 450 ms"); } } [Fact] public async Task WaitOneAsync_WaitsAndThrowsTaskCanceled_WhenNotSignaled_AndCancellationTokenIsCanceled() { var sw = Stopwatch.StartNew(); _cts.CancelAfter(TimeSpan.FromMilliseconds(500)); var exception = await Assert.ThrowsAnyAsync( async () => await TaskExtensions.WaitOneAsync(_mre, Timeout.InfiniteTimeSpan, _cts.Token)); sw.Stop(); #if !NET452 Assert.Equal(_cts.Token, exception.CancellationToken); #else Assert.NotNull(exception); #endif Assert.True(sw.Elapsed > TimeSpan.FromMilliseconds(450), $"Elapsed: {sw.Elapsed.TotalMilliseconds} ms, Expected: 450 ms"); } [Fact] public void WaitOne_ThrowsArgNullException_WhenWaitHandleIsNull() { var exception = Assert.Throws( () => TaskExtensions.WaitOne(null, TimeSpan.Zero, CancellationToken.None)); Assert.Equal("waitHandle", exception.ParamName); } [Fact] public void WaitOne_ThrowsOpCanceledException_WhenCancellationTokenIsCanceled() { _cts.Cancel(); var exception = Assert.Throws( () => TaskExtensions.WaitOne(_mre, TimeSpan.Zero, _cts.Token)); Assert.Equal(_cts.Token, exception.CancellationToken); } [Fact] public void WaitOne_ThrowsOpCanceledException_EvenWhenWaitHandleIsSignaled() { _cts.Cancel(); _mre.Set(); var exception = Assert.Throws( () => TaskExtensions.WaitOne(_mre, Timeout.InfiniteTimeSpan, _cts.Token)); Assert.Equal(_cts.Token, exception.CancellationToken); } [Fact] public void WaitOne_ReturnsTrue_WhenWaitHandleIsSignaled() { _mre.Set(); var result = TaskExtensions.WaitOne(_mre, Timeout.InfiniteTimeSpan, _cts.Token); Assert.True(result); } [Fact] public void WaitOne_ReturnsTrue_WhenWaitHandleIsSignaled_AndTimeoutIsZero() { _mre.Set(); var result = TaskExtensions.WaitOne(_mre, TimeSpan.Zero, _cts.Token); Assert.True(result); } [Fact] public void WaitOne_ReturnsFalseImmediately_WhenNotSignaled_AndTimeoutIsZero() { var result = TaskExtensions.WaitOne(_mre, TimeSpan.Zero, CancellationToken.None); Assert.False(result); } [Fact] public void WaitOne_WaitsAndReturnsFalse_WhenNotSignaled_AndNonNullTimeout() { using (var mre = new ManualResetEvent(false)) { var sw = Stopwatch.StartNew(); var result = TaskExtensions.WaitOne(mre, TimeSpan.FromMilliseconds(500), CancellationToken.None); sw.Stop(); Assert.False(result, "result != false"); Assert.False(mre.WaitOne(TimeSpan.Zero), "mre is signaled"); Assert.True(sw.Elapsed > TimeSpan.FromMilliseconds(450), $"Elapsed: {sw.Elapsed.TotalMilliseconds} ms, Expected: 450 ms"); } } [Fact] public void WaitOne_WaitsAndThrowsTaskCanceled_WhenNotSignaled_AndCancellationTokenIsCanceled() { var sw = Stopwatch.StartNew(); _cts.CancelAfter(TimeSpan.FromMilliseconds(500)); var exception = Assert.ThrowsAny( () => TaskExtensions.WaitOne(_mre, Timeout.InfiniteTimeSpan, _cts.Token)); sw.Stop(); #if !NET452 Assert.Equal(_cts.Token, exception.CancellationToken); #else Assert.NotNull(exception); #endif Assert.True(sw.Elapsed > TimeSpan.FromMilliseconds(450), $"Elapsed: {sw.Elapsed.TotalMilliseconds} ms, Expected: 450 ms"); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Profiling/ProfilerFacts.cs ================================================ using System; using System.Collections.Generic; using Hangfire.Logging; using Hangfire.Profiling; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Profiling { public class ProfilerFacts { private readonly Mock _logger = new Mock(); private readonly object _instance = new object(); public ProfilerFacts() { _logger.Setup(x => x.Log(It.IsAny(), null, null)).Returns(true); } [Theory] [MemberData(nameof(GetProfilers))] internal void InvokeMeasured_ThrowsAnException_WhenActionIsNull(IProfiler profiler) { var exception = Assert.Throws(() => profiler.InvokeMeasured( _instance, null)); Assert.Equal("action", exception.ParamName); } [Theory] [MemberData(nameof(GetProfilers))] internal void InvokeMeasured_ReturnsResult_ForFunctions(IProfiler profiler) { var result = profiler.InvokeMeasured(_instance, x => { Assert.Same(_instance, x); return x.ToString(); }); Assert.Equal(_instance.ToString(), result); } [Theory] [MemberData(nameof(GetProfilers))] internal void InvokeMeasured_DoesNotThrowAnException_WhenInstanceIsNull(IProfiler profiler) { var result = profiler.InvokeMeasured((object)null, x => { Assert.Null(x); return true; }); Assert.True(result); } [Theory] [MemberData(nameof(GetProfilers))] internal void InvokeMeasured_WithAction_InvokesIt(IProfiler profiler) { var invoked = false; profiler.InvokeMeasured(_instance, x => { Assert.Same(_instance, x); invoked = true; }); Assert.True(invoked); } [Theory] [MemberData(nameof(GetProfilers))] internal void InvokeMeasured_WithActionAndNullInstance_InvokesIt(IProfiler profiler) { var invoked = false; profiler.InvokeMeasured((object)null, x => { Assert.Null(x); invoked = true; }); Assert.True(invoked); } [Fact] internal void SlowLog_GeneratesLogMessage_WhenThresholdReached_WithNullMessage() { var profiler = CreateSlowLogProfiler(_logger, TimeSpan.FromSeconds(-1)); profiler.InvokeMeasured(_instance, x => x.ToString()); _logger.Verify(x => x.Log(LogLevel.Warn, It.IsNotNull>(), null), Times.Once); } [Fact] internal void SlowLog_GeneratesLogMessage_WhenThresholdReached_WithNullInstance() { var profiler = CreateSlowLogProfiler(_logger, TimeSpan.FromSeconds(-1)); profiler.InvokeMeasured((object)null, x => true); _logger.Verify(x => x.Log(LogLevel.Warn, It.IsNotNull>(), null), Times.Once); } [Fact] internal void SlowLog_GeneratesLogMessage_WhenThresholdReached_WithNonNullMessage() { var profiler = CreateSlowLogProfiler(_logger, TimeSpan.FromSeconds(-1)); profiler.InvokeMeasured(_instance, x => x.ToString(), _ => "message"); _logger.Verify(x => x.Log(LogLevel.Warn, It.IsNotNull>(), null), Times.Once); } [Fact] internal void SlowLog_DoesNotGenerateLogMessage_WhenThresholdIsNotReached() { var profiler = CreateSlowLogProfiler(_logger, TimeSpan.FromSeconds(600)); profiler.InvokeMeasured(_instance, x => x.ToString(), _ => "message"); _logger.Verify(x => x.Log(LogLevel.Warn, It.IsNotNull>(), null), Times.Never); } public static IEnumerable GetProfilers() { yield return new object[] { EmptyProfiler.Instance }; yield return new object[] { CreateSlowLogProfiler(new Mock(), TimeSpan.FromSeconds(-1)) }; } private static SlowLogProfiler CreateSlowLogProfiler(Mock logger, TimeSpan threshold) { return new SlowLogProfiler(logger.Object, threshold); } } } ================================================ FILE: tests/Hangfire.Core.Tests/QueueAttributeFacts.cs ================================================ using Hangfire.States; using Moq; using Xunit; namespace Hangfire.Core.Tests { public class QueueAttributeFacts { private readonly ElectStateContextMock _context; public QueueAttributeFacts() { _context = new ElectStateContextMock { ApplyContext = { NewStateObject = new EnqueuedState("queue") } }; } [Fact] public void Ctor_CorrectlySets_AllPropertyValues() { var filter = new QueueAttribute("hello"); Assert.Equal("hello", filter.Queue); } [Fact] public void OnStateElection_OverridesTheQueue_OfTheCandidateState() { var filter = new QueueAttribute("override"); filter.OnStateElection(_context.Object); Assert.Equal("override", ((EnqueuedState)_context.Object.CandidateState).Queue); } [Fact] public void OnStateElection_DoesNotDoAnything_IfStateIsNotEnqueuedState() { var filter = new QueueAttribute("override"); var context = new ElectStateContextMock { ApplyContext = { NewState = new Mock() } }; // Does not throw filter.OnStateElection(context.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/RecurringJobEntityFacts.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Hangfire.Common; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests { [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] [SuppressMessage("ReSharper", "ConvertClosureToMethodGroup")] public class RecurringJobEntityFacts { private const string RecurringJobId = "recurring-job-id"; private readonly DateTime _nowInstant = new DateTime(2017, 03, 30, 15, 30, 0, DateTimeKind.Utc); private readonly Dictionary _recurringJob; private readonly Mock _timeZoneResolver; public RecurringJobEntityFacts() { var timeZone = TimeZoneInfo.Local; _recurringJob = new Dictionary { { "Cron", "* * * * *" }, { "Job", InvocationData.SerializeJob(Job.FromExpression(() => Console.WriteLine())).SerializePayload() }, { "TimeZoneId", timeZone.Id } }; _timeZoneResolver = new Mock(); _timeZoneResolver.Setup(x => x.GetTimeZoneById(It.IsAny())).Throws(); _timeZoneResolver.Setup(x => x.GetTimeZoneById(timeZone.Id)).Returns(timeZone); } [Fact] public void Ctor_ThrowsAnException_WhenRecurringJobId_IsNull() { var exception = Assert.Throws(() => new RecurringJobEntity(null, _recurringJob)); Assert.Equal("recurringJobId", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenRecurringJob_IsNull() { var exception = Assert.Throws(() => new RecurringJobEntity(RecurringJobId, null)); Assert.Equal("recurringJob", exception.ParamName); } [Fact] public void Ctor_SetsRelaxedMisfireOption_WhenCorrespondingKeyIsMissing() { var entity = CreateEntity(); Assert.Equal(MisfireHandlingMode.Relaxed, entity.MisfireHandling); } [Fact] public void Ctor_CorrectlyParses_RelaxedMisfireOption_SerializedAsInt() { _recurringJob["Misfire"] = "0"; var entity = CreateEntity(); Assert.Equal(MisfireHandlingMode.Relaxed, entity.MisfireHandling); } [Fact] public void Ctor_CorrectlyParses_StrictMisfireOption_SerializedAsInt() { _recurringJob["Misfire"] = "1"; var entity = CreateEntity(); Assert.Equal(MisfireHandlingMode.Strict, entity.MisfireHandling); } [Fact] public void Ctor_ThrowsAnException_WhenMisfireOption_CanNotBeParsed() { _recurringJob["Misfire"] = "2adgadsg"; Assert.Throws(() => CreateEntity()); } [Fact] public void Ctor_ThrowsAnException_WhenMisfireOption_IsNotWithinAValidRange() { _recurringJob["Misfire"] = "3"; Assert.Throws(() => CreateEntity()); } [Fact] public void IsChanged_DoesNotAddMisfireKey_WhenItIsNotPresentAndDefaultValueIsUnchanged() { var entity = CreateEntity(); entity.IsChanged(_nowInstant, out var fields); Assert.False(fields.ContainsKey("Misfire")); } [Fact] public void IsChanged_DoesNotAddMisfireKey_WhenItIsSetToDefaultAndDefaultValueIsUnchanged() { _recurringJob["Misfire"] = "0"; var entity = CreateEntity(); entity.IsChanged(_nowInstant, out var fields); Assert.False(fields.ContainsKey("Misfire")); } [Fact] public void IsChanged_AddsMisfireKey_WhenItIsNotPresent_ButDefaultValueIsChanged() { var entity = CreateEntity(); entity.MisfireHandling = MisfireHandlingMode.Strict; entity.IsChanged(_nowInstant, out var fields); Assert.True(fields.ContainsKey("Misfire")); Assert.Equal("1", fields["Misfire"]); } [Fact] public void IsChanged_ExplicitlySetsTheDefaultValue_WhenItWasSetToStrict() { _recurringJob["Misfire"] = "1"; var entity = CreateEntity(); entity.MisfireHandling = MisfireHandlingMode.Relaxed; entity.IsChanged(_nowInstant, out var fields); Assert.True(fields.ContainsKey("Misfire")); Assert.Equal("0", fields["Misfire"]); } [Fact] public void IsChanged_DoesNotAddMisfireKey_WhenItsNonDefaultValueIsUnchanged() { _recurringJob["Misfire"] = "1"; var entity = CreateEntity(); entity.MisfireHandling = MisfireHandlingMode.Strict; entity.IsChanged(_nowInstant, out var fields); Assert.False(fields.ContainsKey("Misfire")); } private RecurringJobEntity CreateEntity() { return new RecurringJobEntity(RecurringJobId, _recurringJob); } } } ================================================ FILE: tests/Hangfire.Core.Tests/RecurringJobManagerFacts.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Hangfire.Client; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; #pragma warning disable 618 // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests { public class RecurringJobManagerFacts { private readonly Mock _storage; private readonly string _id; private Job _job; private readonly string _cronExpression; private readonly Mock _connection; private readonly Mock _transaction; private readonly Mock _factory; private readonly Mock _stateMachine; private readonly DateTime _now = new DateTime(2017, 03, 30, 15, 30, 0, DateTimeKind.Utc); private readonly Func _nowFactory; private readonly BackgroundJob _backgroundJob; private readonly Mock _timeZoneResolver; public RecurringJobManagerFacts() { _id = "recurring-job-id"; _job = Job.FromExpression(() => Method()); _backgroundJob = new BackgroundJob("my-id", _job, _now); _cronExpression = Cron.Minutely(); _storage = new Mock(); _factory = new Mock(); _stateMachine = new Mock(); _factory.SetupGet(x => x.StateMachine).Returns(_stateMachine.Object); _nowFactory = () => _now; _timeZoneResolver = new Mock(); _timeZoneResolver.Setup(x => x.GetTimeZoneById(It.IsAny())) .Returns(TimeZoneInfo.FindSystemTimeZoneById); _connection = new Mock(); _storage.Setup(x => x.GetConnection()).Returns(_connection.Object); _transaction = new Mock(); _connection.Setup(x => x.CreateWriteTransaction()).Returns(_transaction.Object); _factory.Setup(x => x.Create(It.Is(ctx => ctx.Storage == _storage.Object && ctx.Connection == _connection.Object && ctx.InitialState == null))) .Returns(_backgroundJob); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new RecurringJobManager(null, _factory.Object)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenFactoryIsNull() { var exception = Assert.Throws( () => new RecurringJobManager(_storage.Object, (IBackgroundJobFactory)null)); Assert.Equal("factory", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenTimeZoneResolverIsNull() { var exception = Assert.Throws( () => new RecurringJobManager(_storage.Object, _factory.Object, null, _nowFactory)); Assert.Equal("timeZoneResolver", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenNowFactoryIsNull() { var exception = Assert.Throws( () => new RecurringJobManager(_storage.Object, _factory.Object, _timeZoneResolver.Object, null)); Assert.Equal("nowFactory", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenIdIsNull() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(null, _job, Cron.Daily())); Assert.Equal("recurringJobId", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenJobIsNull() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, (Job)null, Cron.Daily())); Assert.Equal("job", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenQueueNameIsNull() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, _job, Cron.Daily(), TimeZoneInfo.Local, null)); Assert.Equal("queue", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenCronExpressionIsNull() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, _job, null)); Assert.Equal("cronExpression", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenCronExpressionIsInvalid() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, _job, "* * *")); Assert.Equal("cronExpression", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenCronExpression_HaveInvalidParts() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, _job, "* * * * 9999")); Assert.Equal("cronExpression", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenTimeZoneIsNull() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, _job, _cronExpression, (TimeZoneInfo) null)); Assert.Equal("timeZone", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenOptionsArgumentIsNull() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, _job, _cronExpression, null)); Assert.Equal("options", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenQueueIsNull() { var manager = CreateManager(); var exception = Assert.Throws( () => manager.AddOrUpdate(_id, _job, _cronExpression, TimeZoneInfo.Utc, null)); Assert.Equal("queue", exception.ParamName); } [Fact] public void AddOrUpdate_ThrowsAnException_WhenJobQueueIsSet_ButStorageDoesNotSupportIt() { // Arrange _storage.Setup(x => x.HasFeature(JobStorageFeatures.JobQueueProperty)).Returns(false); _job = Job.FromExpression(() => Method(), "some-queue"); var manager = CreateManager(); // Act & Assert Assert.Throws(() => manager.AddOrUpdate(_id, _job, _cronExpression)); } [Fact] public void AddOrUpdate_AddsAJob_ToTheRecurringJobsSet() { var manager = CreateManager(); manager.AddOrUpdate(_id, _job, _cronExpression); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now))); } [Fact] public void AddOrUpdate_SetsTheRecurringJobEntry() { var manager = CreateManager(); manager.AddOrUpdate(_id, _job, _cronExpression); _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{_id}", It.Is>(rj => rj["Cron"] == "* * * * *" && !String.IsNullOrEmpty(rj["Job"]) && JobHelper.DeserializeDateTime(rj["CreatedAt"]) > _now.AddMinutes(-1)))); } [Fact] public void AddOrUpdate_CommitsTransaction() { var manager = CreateManager(); manager.AddOrUpdate(_id, _job, _cronExpression); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_DoesNotUpdateCreatedAtValue_OfExistingJobs() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")) .Returns(new Dictionary { { "CreatedAt", JobHelper.SerializeDateTime(_now) } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, _cronExpression); // Assert _transaction.Verify( x => x.SetRangeInHash( $"recurring-job:{_id}", It.Is>(rj => rj.ContainsKey("CreatedAt"))), Times.Never); } [Fact] public void AddOrUpdate_IsAbleToScheduleSecondBasedCronExpression() { var manager = CreateManager(); manager.AddOrUpdate(_id, _job, "15 * * * * *"); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddSeconds(15)))); } [Fact] public void AddOrUpdate_IsAbleToScheduleMacroBasedCronExpression() { var manager = CreateManager(); manager.AddOrUpdate(_id, _job, "@hourly"); _transaction.Verify(x => x.AddToSet( "recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddMinutes(30)))); } [Fact] public void AddOrUpdate_EnsuresExistingOldJobsAreUpdated() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", _cronExpression }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "CreatedAt", JobHelper.SerializeDateTime(_now) }, { "NextExecution", JobHelper.SerializeDateTime(_now) }, { "Queue", "default" }, { "TimeZoneId", "UTC" }, { "LastJobId", "1384" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, _cronExpression); // Assert _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{_id}", It.Is>(dict => dict.Count == 1 && dict["V"] == "2"))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_CanAddRecurringJob_WithCronThatNeverFires() { // Arrange var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "0 0 31 2 *"); // Assert _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{_id}", It.Is>(dict => dict.ContainsKey("Cron") && dict["Cron"] == "0 0 31 2 *" && !dict.ContainsKey("NextExecution")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, -1.0D)); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_CanResumeRecurringJob_ThatNeverFires() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "0 0 31 2 *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddDays(-1)) }, { "NextExecution", String.Empty }, { "TimeZoneId", "UTC" }, { "LastJobId", "1384" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{_id}", It.Is>(dict => dict.ContainsKey("Cron") && dict["Cron"] == "* * * * *" && dict.ContainsKey("NextExecution") && JobHelper.DeserializeDateTime(dict["NextExecution"]) == _now))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_UsesTimeZoneResolver_WhenCalculatingNextExecution() { // Arrange var timeZone = TimeZoneInfo.FindSystemTimeZoneById(PlatformHelper.IsRunningOnWindows() ? "Hawaiian Standard Time" : "Pacific/Honolulu"); _timeZoneResolver.Setup(x => x.GetTimeZoneById(It.IsAny())).Throws(); _timeZoneResolver .Setup(x => x.GetTimeZoneById(It.Is(id => id == "Hawaiian Standard Time" || id == "Pacific/Honolulu"))) .Returns(timeZone); // We are returning IANA time zone on Windows and Windows time zone on Linux. _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "0 0 * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "CreatedAt", JobHelper.SerializeDateTime(_now) }, { "TimeZoneId", PlatformHelper.IsRunningOnWindows() ? "Pacific/Honolulu" : "Hawaiian Standard Time" }, { "NextExecution", JobHelper.SerializeDateTime(_now.AddHours(18).AddMinutes(30)) }, { "Queue", "default" }, { "V", "2" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "0 0 * * *", timeZone, "default"); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>(dict => dict.ContainsKey("TimeZoneId") && !dict.ContainsKey("NextExecution")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddHours(18).AddMinutes(30)))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_DoesNotReScheduleJob_WhenUpdatingIt() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "* * * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddMinutes(-3)) }, { "LastExecution", JobHelper.SerializeDateTime(_now.AddMinutes(-2)) }, { "NextExecution", JobHelper.SerializeDateTime(_now.AddMinutes(-1)) } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>(dict => !dict.ContainsKey("NextExecution") || dict["NextExecution"] == JobHelper.SerializeDateTime(_now.AddMinutes(-1))))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddMinutes(-1)))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_CanUpdateRecurringJobs_WhoseMethodCouldNotBeFound() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "* * * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload().Replace("Hangfire", "Test") }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddMinutes(-2)) }, { "LastExecution", JobHelper.SerializeDateTime(_now.AddMinutes(-1)) }, { "NextExecution", JobHelper.SerializeDateTime(_now) } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>( dict => dict.ContainsKey("Job") && dict["Job"].Contains("Hangfire.Core.Tests")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_CanUpdateRecurringJobs_WhoseJobPropertyCanNotBeDeserialized() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "* * * * *" }, { "Job", "some garbage" }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddMinutes(-2)) }, { "LastExecution", JobHelper.SerializeDateTime(_now.AddMinutes(-1)) }, { "NextExecution", JobHelper.SerializeDateTime(_now) } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>( dict => dict.ContainsKey("Job") && dict["Job"].Contains("Hangfire.Core.Tests")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_ResetsRetryAttemptNumber_WhenUpdatingARecurringJob() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "* * * * *" }, { "Job", "some garbage" }, { "RetryAttempt", "10" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>( dict => dict.ContainsKey("RetryAttempt") && dict["RetryAttempt"] == "0"))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_UsesCurrentTime_InsteadOfLastExecution_ToCalculateNextExecution_WhenChangingCronExpression() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "30 12 * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "LastExecution", JobHelper.SerializeDateTime(_now.AddHours(-3)) } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "30 13 * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>(dict => JobHelper.DeserializeDateTime(dict["NextExecution"]) == _now.AddHours(22)))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddHours(22)))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_UsesCurrentTime_InsteadOfLastExecution_ToCalculateNextExecution_WhenChangingTimeZone() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "30 13 * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "TimeZoneId", PlatformHelper.IsRunningOnWindows() ? "Pacific/Honolulu" : "Hawaiian Standard Time" }, { "LastExecution", JobHelper.SerializeDateTime(_now.AddDays(-3)) } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "30 13 * * *", TimeZoneInfo.Utc); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>(dict => JobHelper.DeserializeDateTime(dict["NextExecution"]) == _now.AddHours(22)))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddHours(22)))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_CanRecoverARecurringJob_FromErrorState_WithoutSchedulingToThePast() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "* * * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddDays(-1)) }, { "Error", "Some error that's gone" }, { "NextExecution", String.Empty }, { "TimeZoneId", "UTC" }, { "LastJobId", "1384" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{_id}", It.Is>(dict => dict.ContainsKey("Error") && dict["Error"] == String.Empty && dict.ContainsKey("NextExecution") && JobHelper.DeserializeDateTime(dict["NextExecution"]) == _now))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_CanNotTriggerRecurringJob_WhenNextExecutionTimeIsInFuture() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "* * * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddDays(-1)) }, { "LastExecution", JobHelper.SerializeDateTime(_now.AddMinutes(-5)) }, { "NextExecution", JobHelper.SerializeDateTime(_now.AddMinutes(1)) }, { "TimeZoneId", "UTC" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{_id}", It.Is>(dict => !dict.ContainsKey("NextExecution") || dict["NextExecution"] == JobHelper.SerializeDateTime(_now.AddMinutes(1))))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddMinutes(1)))); _transaction.Verify(x => x.Commit()); } [Fact] public void AddOrUpdate_DoesNotUpdate_UnchangedRecurringJob() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Queue", "default" }, { "Cron", "* * * * *" }, { "Job", InvocationData.SerializeJob(_job).SerializePayload() }, { "TimeZoneId", "UTC" }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddDays(-1)) }, { "V", "2" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify( x => x.SetRangeInHash(It.IsAny(), It.IsAny>>()), Times.Never); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void AddOrUpdate_DoesNotUpdateRecurringJob_WhenErrorFieldIsSetToEmptyString() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Queue", "default" }, { "Cron", "* * * * *" }, { "Job", InvocationData.SerializeJob(_job).SerializePayload() }, { "TimeZoneId", "UTC" }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddDays(-1)) }, { "V", "2" }, { "Error", "" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify( x => x.SetRangeInHash(It.IsAny(), It.IsAny>>()), Times.Never); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void AddOrUpdate_DoesNotUpdateRecurringJob_WhenLastJobIdFieldIsSetToEmptyString() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Queue", "default" }, { "Cron", "* * * * *" }, { "Job", InvocationData.SerializeJob(_job).SerializePayload() }, { "TimeZoneId", "UTC" }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddDays(-1)) }, { "V", "2" }, { "LastJobId", "" } }); var manager = CreateManager(); // Act manager.AddOrUpdate(_id, _job, "* * * * *"); // Assert _transaction.Verify( x => x.SetRangeInHash(It.IsAny(), It.IsAny>>()), Times.Never); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void Trigger_ThrowsAnException_WhenIdIsNull() { var manager = CreateManager(); Assert.Throws(() => manager.Trigger(null)); } [Fact] public void Trigger_EnqueuesScheduledJob() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")) .Returns(new Dictionary { { "Job", JobHelper.ToJson(InvocationData.Serialize(Job.FromExpression(() => Console.WriteLine()))) }, { "Cron", Cron.Minutely() } }); var manager = CreateManager(); // Act manager.Trigger(_id); // Assert _stateMachine.Verify(x => x.ApplyState( It.Is(context => context.NewState is EnqueuedState))); } [Fact] public void Trigger_EnqueuedJobToTheSpecificQueue_IfSpecified() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")) .Returns(new Dictionary { { "Job", JobHelper.ToJson(InvocationData.Serialize(_job)) }, { "Cron", _cronExpression }, { "Queue", "my_queue" } }); var manager = CreateManager(); // Act manager.Trigger(_id); // Assert _stateMachine.Verify(x => x.ApplyState(It.Is(context => ((EnqueuedState)context.NewState).Queue == "my_queue"))); } [Fact] public void Trigger_DoesNotThrowIfJobDoesNotExist() { var manager = CreateManager(); manager.Trigger(_id); _factory.Verify(x => x.Create(It.IsAny()), Times.Never); } [Fact] public void Trigger_CanTriggerRecurringJob_WithCronThatNeverFires() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")) .Returns(new Dictionary { { "Job", JobHelper.ToJson(InvocationData.Serialize(_job)) }, { "Cron", "0 0 31 2 *" }, }); var manager = CreateManager(); // Act manager.Trigger(_id); // Assert _stateMachine.Verify(x => x.ApplyState(It.IsAny())); _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>(dict => dict.ContainsKey("LastExecution") && dict["LastExecution"] == JobHelper.SerializeDateTime(_now) && !dict.ContainsKey("NextExecution")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, -1.0D)); _transaction.Verify(x => x.Commit()); } [Fact] public void Trigger_SchedulesNextExecution_DependingOnCurrentTime_ToTheFuture() { // Arrange _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")).Returns(new Dictionary { { "Cron", "* * * * *" }, { "Job", InvocationData.Serialize(_job).SerializePayload() }, { "CreatedAt", JobHelper.SerializeDateTime(_now.AddMinutes(-3)) }, { "LastExecution", JobHelper.SerializeDateTime(_now.AddMinutes(-2)) }, { "NextExecution", JobHelper.SerializeDateTime(_now.AddMinutes(-1)) } }); var manager = CreateManager(); // Act manager.Trigger(_id); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{_id}", It.Is>(dict => dict["NextExecution"] == JobHelper.SerializeDateTime(_now.AddMinutes(1))))); _transaction.Verify(x => x.AddToSet("recurring-jobs", _id, JobHelper.ToTimestamp(_now.AddMinutes(1)))); _transaction.Verify(x => x.Commit()); } [Fact] public void Trigger_ThrowsAnException_WhenRecurringJobCanNotBeTriggered_AndDoesNotCreateBackgroundJob() { // Arrange _timeZoneResolver.Setup(x => x.GetTimeZoneById(It.IsAny())).Throws(); _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{_id}")) .Returns(new Dictionary { { "Job", JobHelper.ToJson(InvocationData.Serialize(Job.FromExpression(() => Console.WriteLine()))) }, { "Cron", Cron.Minutely() }, { "TimeZoneId", "UnexistingID" } }); var manager = CreateManager(); // Act Assert.Throws(() => manager.Trigger(_id)); // Assert _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Never); } [Fact] public void RemoveIfExists_ThrowsAnException_WhenIdIsNull() { var manager = CreateManager(); Assert.Throws( () => manager.RemoveIfExists(null)); } [Fact] public void RemoveIfExists_RemovesEntriesAndCommitsTheTransaction() { var manager = CreateManager(); manager.RemoveIfExists(_id); _transaction.Verify(x => x.RemoveFromSet("recurring-jobs", _id)); _transaction.Verify(x => x.RemoveHash($"recurring-job:{_id}")); _transaction.Verify(x => x.Commit()); } [Fact, CleanSerializerSettings] public void HandlesChangingProcessOfInvocationDataSerialization() { SerializationHelper.SetUserSerializerSettings(SerializerSettingsHelper.DangerousSettings); var initialJob = Job.FromExpression(() => Console.WriteLine()); var invocationData = InvocationData.Serialize(initialJob); var serializedInvocationData = SerializationHelper.Serialize(invocationData, SerializationOption.User); var deserializedInvocationData = SerializationHelper.Deserialize(serializedInvocationData); var deserializedJob = deserializedInvocationData.Deserialize(); Assert.Equal(initialJob.Args, deserializedJob.Args); Assert.Equal(initialJob.Method, deserializedJob.Method); Assert.Equal(initialJob.Type, deserializedJob.Type); } private RecurringJobManager CreateManager() { return new RecurringJobManager(_storage.Object, _factory.Object, _timeZoneResolver.Object, _nowFactory); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Method() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/RecurringJobOptionsFacts.cs ================================================ using System; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests { public class RecurringJobOptionsFacts { [Fact] public void Ctor_SetTheDefaultValues_ForProperties() { var options = new RecurringJobOptions(); Assert.Equal(TimeZoneInfo.Utc, options.TimeZone); Assert.Equal("default", options.QueueName); Assert.Equal(MisfireHandlingMode.Relaxed, options.MisfireHandling); } [Fact] public void SetTimeZone_ThrowsAnException_WhenValueIsNull() { var options = new RecurringJobOptions(); Assert.Throws(() => options.TimeZone = null); } [Fact] public void SetQueueName_ThrowsAnException_WhenValueIsNull() { var options = new RecurringJobOptions(); Assert.Throws(() => options.QueueName = null); } [Fact] public void SetQueueName_ThrowsAnException_WhenQueueNameHasInvalidFormat() { var options = new RecurringJobOptions(); var exception = Assert.Throws( () => options.QueueName = "UPPER_CASE"); Assert.Equal("value", exception.ParamName); } } } ================================================ FILE: tests/Hangfire.Core.Tests/RetryAttributeFacts.cs ================================================ using System; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests { public class RetryAttributeFacts { private const string JobId = "id"; private readonly FailedState _failedState; private readonly Mock _connection; private readonly ElectStateContextMock _context; private readonly Mock _transaction; public RetryAttributeFacts() { _failedState = new FailedState(new InvalidOperationException()); _connection = new Mock(); _transaction = new Mock(); _context = new ElectStateContextMock(); _context.ApplyContext.BackgroundJob.Id = JobId; _context.ApplyContext.Connection = _connection; _context.ApplyContext.NewStateObject = _failedState; } [Fact] public void Ctor_SetsPositiveRetryAttemptsNumber_ByDefault() { var filter = new AutomaticRetryAttribute(); Assert.Equal(10, filter.Attempts); } [Fact] public void Ctor_ThrowsAnException_WhenAttemptsValueIsNegative() { Assert.Throws( () => new AutomaticRetryAttribute { Attempts = -1 }); } [Fact] public void Ctor_ThrowsAnException_WhenDelaysInSecondsIsEmpty() { Assert.Throws( () => new AutomaticRetryAttribute { DelaysInSeconds = new int[0] }); } [Fact] public void Ctor_ThrowsAnException_WhenDelaysInSecondsContainsNegativeNumbers() { Assert.Throws( () => new AutomaticRetryAttribute { DelaysInSeconds = new [] { 1, -5 } }); } [Fact] public void Ctor_ThrowsAnException_WhenDelayByAttemptIsNull() { Assert.Throws( () => new AutomaticRetryAttribute { DelayInSecondsByAttemptFunc = null }); } [Fact] public void Ctor_SetsOnAttemptsExceededAction_ByDefault() { var filter = new AutomaticRetryAttribute(); Assert.Equal(AttemptsExceededAction.Fail, filter.OnAttemptsExceeded); } [Fact] public void Ctor_DelayByAttemptIsNotNull_ByDefault() { var filter = new AutomaticRetryAttribute(); Assert.NotNull(filter.DelayInSecondsByAttemptFunc); } [Fact] public void DelaysInSeconds_SetsValueCorrectly() { var filter = new AutomaticRetryAttribute { DelaysInSeconds = new[] { 5, 8 } }; Assert.Equal(2, filter.DelaysInSeconds.Length); Assert.Equal(5, filter.DelaysInSeconds[0]); Assert.Equal(8, filter.DelaysInSeconds[1]); } [Fact] public void DelaysInSeconds_CanBeSetToNull() { var filter = new AutomaticRetryAttribute { DelaysInSeconds = null }; Assert.NotNull(filter); } [Fact] public void DelayInSecondsByAttemptFunc_ReturnCorrectValue_WhenCustomFunctionIsSet() { var filter = new AutomaticRetryAttribute { DelayInSecondsByAttemptFunc = attempt => (int)attempt % 3 }; Assert.Equal(1, filter.DelayInSecondsByAttemptFunc(1)); Assert.Equal(2, filter.DelayInSecondsByAttemptFunc(2)); Assert.Equal(0, filter.DelayInSecondsByAttemptFunc(3)); Assert.Equal(1, filter.DelayInSecondsByAttemptFunc(4)); Assert.Equal(2, filter.DelayInSecondsByAttemptFunc(5)); Assert.Equal(1, filter.DelayInSecondsByAttemptFunc(100)); } [Fact] public void OnStateElection_ThrowsAnException_WhenDelayInSecondsByAttemptFuncThrowsAnException() { var exception = new Exception(); var filter = new AutomaticRetryAttribute { DelayInSecondsByAttemptFunc = attempt => { throw exception; } }; var thrownException = Assert.Throws(() => filter.OnStateElection(_context.Object)); Assert.Equal(exception, thrownException); } [Fact] public void OnStateElection_UsesDelaysInSeconds_WhenBothDelaysInSecondsAndDelayInSecondsByAttemptFuncAreSpecified() { var filter = new AutomaticRetryAttribute { DelayInSecondsByAttemptFunc = attempt => 1, DelaysInSeconds = new[] { 0 } }; filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); } [Fact] public void OnStateElection_DoesNotChangeState_IfRetryAttemptsIsSetToZero() { var filter = new AutomaticRetryAttribute { Attempts = 0 }; filter.OnStateElection(_context.Object); Assert.Same(_failedState, _context.Object.CandidateState); } [Fact] public void OnStateElection_ChangeStateToScheduled_IfRetryAttemptsWereNotExceeded() { var filter = CreateFilter(); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); Assert.True(((ScheduledState)_context.Object.CandidateState).EnqueueAt > DateTime.UtcNow); Assert.NotNull(_context.Object.CandidateState.Reason); Assert.Contains("1 of 1", _context.Object.CandidateState.Reason); _connection.Verify(x => x.SetJobParameter(JobId, "RetryCount", "1")); } [Fact] public void OnStateElection_ChangeStateToEnqueued_IfDelayIsZero() { var filter = new AutomaticRetryAttribute { Attempts = 1, DelaysInSeconds = new[] { 0 } }; filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); Assert.NotNull(_context.Object.CandidateState.Reason); Assert.Contains("1 of 1", _context.Object.CandidateState.Reason); _connection.Verify(x => x.SetJobParameter(JobId, "RetryCount", "1")); } [Fact] public void OnStateElection_DoesNotChangeAnything_IfCandidateStateIsNotFailedState() { var filter = CreateFilter(); var state = new Mock(); _context.ApplyContext.NewStateObject = null; _context.ApplyContext.NewState = state; filter.OnStateElection(_context.Object); Assert.Same(state.Object, _context.Object.CandidateState); } [Fact] public void OnStateElection_DoesNotChangeState_IfRetryAttemptsNumberExceeded() { _connection.Setup(x => x.GetJobParameter(JobId, "RetryCount")).Returns("1"); var filter = CreateFilter(); filter.OnStateElection(_context.Object); Assert.Same(_failedState, _context.Object.CandidateState); } [Fact] public void OnStateElection_ChangesStateToDeleted_IfRetryAttemptsNumberExceededAndOnAttemptsExceededIsSetToDelete() { _connection.Setup(x => x.GetJobParameter(JobId, "RetryCount")).Returns("1"); var filter = new AutomaticRetryAttribute { Attempts = 1, OnAttemptsExceeded = AttemptsExceededAction.Delete }; filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); } [Fact] public void OnStateElection_ChangesStateToFailed_IfRetryAttemptsNumberExceededAndOnAttemptsExceedIsSetToFail() { _connection.Setup(x => x.GetJobParameter(JobId, "RetryCount")).Returns("1"); var filter = new AutomaticRetryAttribute { Attempts = 1, OnAttemptsExceeded = AttemptsExceededAction.Fail }; filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); } [Fact] public void OnStateElection_ChangesStateToDeleted_IfRetryAttemptsNumberIsZeroAndOnAttemptsExceedIsSetToDelete() { _connection.Setup(x => x.GetJobParameter(JobId, "RetryCount")).Returns("0"); var filter = new AutomaticRetryAttribute { Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete }; filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); } [Fact] public void OnStateElection_DoesNotDoAnything_WhenOnlyOnIsSpecified_AndIneligibleExceptionIsPassed() { var filter = new AutomaticRetryAttribute { OnlyOn = new[] { typeof(FormatException), typeof(NullReferenceException) } }; _context.ApplyContext.NewStateObject = new FailedState(new ArgumentException()); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); _connection.Verify(x => x.GetJobParameter(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void OnStateElection_TriggersRetryAnyway_WhenEmptyOnlyOnIsSpecified() { var filter = new AutomaticRetryAttribute { OnlyOn = Type.EmptyTypes }; _context.ApplyContext.NewStateObject = new FailedState(new ArgumentException()); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); _connection.Verify(x => x.SetJobParameter(JobId, "RetryCount", "1")); } [Fact] public void OnStateElection_WorksAsExpected_WhenOnlyOnIsSpecified_AndAnEligibleExceptionIsPassed() { var filter = new AutomaticRetryAttribute { OnlyOn = new[] { typeof(NullReferenceException), typeof(InvalidOperationException) } }; filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); _connection.Verify(x => x.SetJobParameter(JobId, "RetryCount", "1")); } [Fact] public void OnStateElection_WorksAsExpected_WhenOnlyOnIsSpecified_AndADerivedClassOfAnEligibleExceptionIsPassed() { var filter = new AutomaticRetryAttribute { OnlyOn = new[] { typeof(ArgumentException) } }; _context.ApplyContext.NewStateObject = new FailedState(new ArgumentNullException()); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); _connection.Verify(x => x.SetJobParameter(JobId, "RetryCount", "1")); } [Fact] public void OnStateElection_WorksAsExpected_WhenExceptOnIsSpecified_AndIneligibleExceptionIsPassed() { var filter = new AutomaticRetryAttribute { ExceptOn = new[] { typeof(FormatException), typeof(NullReferenceException) } }; _context.ApplyContext.NewStateObject = new FailedState(new ArgumentException()); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); _connection.Verify(x => x.SetJobParameter(JobId, "RetryCount", "1")); } [Fact] public void OnStateElection_DoesNotDoAnything_WhenExceptOnIsSpecified_AndAnEligibleExceptionIsPassed() { var filter = new AutomaticRetryAttribute { ExceptOn = new[] { typeof(NullReferenceException), typeof(InvalidOperationException) } }; filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); _connection.Verify(x => x.GetJobParameter(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void OnStateElection_DoesNotDoAnything_WhenExceptOnIsSpecified_AndADerivedClassOfAnEligibleExceptionIsPassed() { var filter = new AutomaticRetryAttribute { ExceptOn = new[] { typeof(ArgumentException) } }; _context.ApplyContext.NewStateObject = new FailedState(new ArgumentNullException()); filter.OnStateElection(_context.Object); Assert.IsType(_context.Object.CandidateState); _connection.Verify(x => x.GetJobParameter(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void OnStateApplied_AddsJobToRetriesSet_IfNewStateIsScheduled() { // Arrange var filter = CreateFilter(); var newState = new ScheduledState(DateTime.UtcNow) { Reason = "Retry attempt ..." }; var applyStateContext = CreatApplyStateContext(newState); // Act filter.OnStateApplied(applyStateContext, _transaction.Object); // Assert _transaction.Verify(t => t.AddToSet("retries", JobId)); } [Fact] public void OnStateApplied_DoesNotAddJobToRetriesSet_IfNewStateIsScheduledAndReasonIsNull() { // Arrange var filter = CreateFilter(); var newState = new ScheduledState(DateTime.UtcNow) { Reason = null }; var applyStateContext = CreatApplyStateContext(newState); // Act filter.OnStateApplied(applyStateContext, _transaction.Object); // Assert _transaction.Verify(t => t.AddToSet(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void OnStateApplied_DoesNotAddJobToRetriesSet_IfNewStateIsScheduledAndReasonDoesNotMatch() { // Arrange var filter = CreateFilter(); var newState = new ScheduledState(DateTime.UtcNow) { Reason = "Some reason." }; var applyStateContext = CreatApplyStateContext(newState); // Act filter.OnStateApplied(applyStateContext, _transaction.Object); // Assert _transaction.Verify(t => t.AddToSet(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void OnStateApplied_DoesNotAddJobToRetriesSet_IfNewStateIsEnqueued() { // Arrange var filter = CreateFilter(); var newState = new EnqueuedState { Reason = "Retry attempt ..." }; var applyStateContext = CreatApplyStateContext(newState); // Act filter.OnStateApplied(applyStateContext, _transaction.Object); // Assert _transaction.Verify(t => t.AddToSet(It.IsAny(), It.IsAny()), Times.Never); } private static AutomaticRetryAttribute CreateFilter() { return new AutomaticRetryAttribute { Attempts = 1 }; } private ApplyStateContext CreatApplyStateContext(IState newState) { var context = new ApplyStateContextMock(); context.BackgroundJob.Id = JobId; context.Transaction = _transaction; context.NewStateObject = newState; return context.Object; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/BackgroundJobPerformerFacts.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Hangfire.Common; using Hangfire.Server; using Moq; using Moq.Sequences; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Server { public class BackgroundJobPerformerFacts { private readonly PerformContextMock _context; private readonly Mock _innerPerformer; private readonly IList _filters; private readonly Mock _filterProvider; public BackgroundJobPerformerFacts() { _context = new PerformContextMock(); _innerPerformer = new Mock(); _filters = new List(); _filterProvider = new Mock(); _filterProvider.Setup(x => x.GetFilters(It.IsNotNull())).Returns( _filters.Select(f => new JobFilter(f, JobFilterScope.Type, null))); } [Fact] public void Ctor_ThrowsAnException_WhenFilterProvider_IsNull() { var exception = Assert.Throws( () => new BackgroundJobPerformer(null, _innerPerformer.Object)); Assert.Equal("filterProvider", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenInnerPerformer_IsNull() { var exception = Assert.Throws( () => new BackgroundJobPerformer(_filterProvider.Object, (IBackgroundJobPerformer)null)); Assert.Equal("innerPerformer", exception.ParamName); } [Fact] public void Run_ThrowsAnException_WhenContextIsNull() { var performer = CreatePerformer(); var exception = Assert.Throws( () => performer.Perform(null)); Assert.Equal("context", exception.ParamName); } [Fact] public void Run_CallsTheRunMethod_OfInnerProcess() { var performer = CreatePerformer(); performer.Perform(_context.Object); _innerPerformer.Verify(x => x.Perform(_context.Object), Times.Once); } [Fact] public void Run_StoresJobReturnValueInPerformedContext() { // Arrange var filter = CreateFilter(); var performer = CreatePerformer(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Returns("Returned value"); // Act performer.Perform(_context.Object); // Assert filter.Verify( x => x.OnPerformed(It.Is(context => (string)context.Result == "Returned value"))); } [Fact] public void Run_ReturnsValueReturnedByJob() { // Arrange // ReSharper disable once UnusedVariable var filter = CreateFilter(); var performer = CreatePerformer(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Returns("Returned value"); // Act var result = performer.Perform(_context.Object); // Assert Assert.Equal("Returned value", result); } [Fact] public void Run_DoesNotCatchExceptions() { // Arrange _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws(() => performer.Perform(_context.Object)); } [Fact] public void Run_CallsExceptionFilter_OnException() { // Arrange var filter = CreateFilter(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws(() => performer.Perform(_context.Object)); filter.Verify(x => x.OnServerException(It.Is(context => context.Exception is InvalidOperationException))); } [Fact, Sequence] public void Run_CallsExceptionFilters_InReverseOrder() { // Arrange var filter1 = CreateFilter(); var filter2 = CreateFilter(); filter2.Setup(x => x.OnServerException(It.IsAny())).InSequence(); filter1.Setup(x => x.OnServerException(It.IsAny())).InSequence(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var performer = CreatePerformer(); // Act Assert.Throws(() => performer.Perform(_context.Object)); // Assert - see the `SequenceAttribute` class. } [Fact] public void Run_EatsException_WhenItWasHandlerByFilter() { // Arrange _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var filter = CreateFilter(); filter.Setup(x => x.OnServerException(It.IsAny())) .Callback((ServerExceptionContext x) => x.ExceptionHandled = true); var performer = CreatePerformer(); // Act & Assert does not throw performer.Perform(_context.Object); } [Fact, Sequence] public void Run_CallsServerFilters_BeforeAndAfterTheCreationOfAJob() { // Arrange var filter = CreateFilter(); filter.Setup(x => x.OnPerforming(It.IsNotNull())) .InSequence(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .InSequence(); filter.Setup(x => x.OnPerformed(It.IsNotNull())) .InSequence(); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert - see the `SequenceAttribute` class. } [Fact, Sequence] public void Run_WrapsFilterCalls_OneIntoAnother() { // Arrange var outerFilter = CreateFilter(); var innerFilter = CreateFilter(); outerFilter.Setup(x => x.OnPerforming(It.IsAny())).InSequence(); innerFilter.Setup(x => x.OnPerforming(It.IsAny())).InSequence(); innerFilter.Setup(x => x.OnPerformed(It.IsAny())).InSequence(); outerFilter.Setup(x => x.OnPerformed(It.IsAny())).InSequence(); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert - see the `SequenceAttribute` class. } [Fact] public void Run_DoesNotCallBoth_Perform_And_OnPerforming_WhenFilterCancelsThis() { // Arrange var filter = CreateFilter(); filter.Setup(x => x.OnPerforming(It.IsAny())) .Callback((PerformingContext x) => x.Canceled = true); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert _innerPerformer.Verify(x => x.Perform(_context.Object), Times.Never); filter.Verify(x => x.OnPerformed(It.IsAny()), Times.Never); } [Fact] public void Run_TellsOuterFilter_AboutTheCancellationOfCreation() { // Arrange var outerFilter = CreateFilter(); var innerFilter = CreateFilter(); innerFilter.Setup(x => x.OnPerforming(It.IsAny())) .Callback((PerformingContext context) => context.Canceled = true); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert outerFilter.Verify(x => x.OnPerformed(It.Is(context => context.Canceled))); } [Fact] public void Run_DoesNotCall_Perform_And_OnPerformed_WhenExceptionOccured_DuringPerformingPhase() { // Arrange var filter = CreateFilter(); filter.Setup(x => x.OnPerforming(It.IsAny())) .Throws(); var performer = CreatePerformer(); // Act var exception = Assert.Throws( () => performer.Perform(_context.Object)); // Assert Assert.IsType(exception.InnerException); _innerPerformer.Verify(x => x.Perform(It.IsAny()), Times.Never); filter.Verify(x => x.OnPerformed(It.IsAny()), Times.Never); } [Fact] public void Run_TellsFiltersAboutException_WhenItIsOccured_DuringThePerformanceOfAJob() { // Arrange var filter = CreateFilter(); var exception = new InvalidOperationException(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(exception); var performer = CreatePerformer(); // Act Assert.Throws(() => performer.Perform(_context.Object)); // Assert filter.Verify(x => x.OnPerformed(It.Is( context => context.Exception == exception))); } [Fact] public void Run_TellsOuterFilters_AboutAllExceptions() { // Arrange var outerFilter = CreateFilter(); // ReSharper disable once UnusedVariable var innerFilter = CreateFilter(); var exception = new InvalidOperationException(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(exception); var performer = CreatePerformer(); // Act Assert.Throws(() => performer.Perform(_context.Object)); outerFilter.Verify(x => x.OnPerformed(It.Is(context => context.Exception == exception))); } [Fact] public void Run_DoesNotThrow_HandledExceptions() { // Arrange var filter = CreateFilter(); var exception = new InvalidOperationException(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(exception); filter.Setup(x => x.OnPerformed(It.Is(context => context.Exception == exception))) .Callback((PerformedContext x) => x.ExceptionHandled = true); var performer = CreatePerformer(); // Act & Assert does not throw performer.Perform(_context.Object); } [Fact] public void Run_TellsOuterFilter_EvenAboutHandledException() { // Arrange var outerFilter = CreateFilter(); var innerFilter = CreateFilter(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); innerFilter.Setup(x => x.OnPerformed(It.IsAny())) .Callback((PerformedContext x) => x.ExceptionHandled = true); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert outerFilter.Verify(x => x.OnPerformed(It.Is(context => context.Exception != null))); } [Fact] public void Run_WrapsOnPerformedException_IntoJobPerformanceException() { // Arrange var filter = CreateFilter(); filter.Setup(x => x.OnPerformed(It.IsAny())) .Throws(); var performer = CreatePerformer(); // Act & Assert var exception = Assert.Throws(() => performer.Perform(_context.Object)); Assert.IsType(exception.InnerException); } [Fact] public void Run_WrapsOnPerformedException_OccuredAfterAnotherException_IntoJobPerformanceException() { // Arrange var filter = CreateFilter(); filter.Setup(x => x.OnPerformed(It.IsAny())) .Throws(); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var performer = CreatePerformer(); // Act & Assert var exception = Assert.Throws(() => performer.Perform(_context.Object)); Assert.IsType(exception.InnerException); } [Fact] public void Run_ExceptionFiltersAreNOTInvoked_OnJobAbortedException() { // Arrange _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var filter = CreateFilter(); var performer = CreatePerformer(); // Act Assert.Throws(() => performer.Perform(_context.Object)); // Assert filter.Verify( x => x.OnServerException(It.IsAny()), Times.Never); } [Fact] public void Run_ExceptionFiltersAreNOTInvoked_OnOperationCanceledException_WhenShutdownTokenIsCanceled() { // Arrange var cts = new CancellationTokenSource(); cts.Cancel(); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(cts.Token); _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var filter = CreateFilter(); var performer = CreatePerformer(); // Act Assert.Throws( () => performer.Perform(_context.Object)); // Assert filter.Verify( x => x.OnServerException(It.IsAny()), Times.Never); } [Fact] public void Run_ExceptionFiltersAreInvoked_OnOperationCanceledException_WhenShutdownTokenIsNOTCanceled() { // Arrange _innerPerformer .Setup(x => x.Perform(_context.Object)) .Throws(); var filter = CreateFilter(); var performer = CreatePerformer(); // Act Assert.Throws( () => performer.Perform(_context.Object)); // Assert filter.Verify( x => x.OnServerException(It.IsAny()), Times.Once); } [Fact] public void Run_ThrowsOperationCanceledException_OccurredInPreFilterMethods_WhenShutdownTokenIsCanceled() { // Arrange var cts = new CancellationTokenSource(); cts.Cancel(); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(cts.Token); var filter = CreateFilter(); filter.Setup(x => x.OnPerforming(It.IsAny())) .Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws( () => performer.Perform(_context.Object)); } [Fact] public void Run_ThrowsJobPerformanceException_InsteadOfOperationCanceled_OccurredInPreFilterMethods_WhenShutdownTokenIsNotCanceled() { // Arrange var filter = CreateFilter(); filter.Setup(x => x.OnPerforming(It.IsAny())) .Throws(); var performer = CreatePerformer(); // Act var exception = Assert.Throws( () => performer.Perform(_context.Object)); // Assert Assert.IsType(exception.InnerException); } [Fact] public void Run_ThrowsOperationCanceledException_OccurredInPostFilterMethods_WhenShutdownTokenIsCanceled() { // Arrange var cts = new CancellationTokenSource(); cts.Cancel(); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(cts.Token); var filter = CreateFilter(); filter.Setup(x => x.OnPerformed(It.IsAny())) .Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws(() => performer.Perform(_context.Object)); } [Fact] public void Run_ThrowsJobPerformanceException_InsteadOfOperationCanceled_OccurredInPostFilterMethods_WhenShutdownTokenIsNOTCanceled() { // Arrange var filter = CreateFilter(); filter.Setup(x => x.OnPerformed(It.IsAny())) .Throws(); var performer = CreatePerformer(); // Act var exception = Assert.Throws( () => performer.Perform(_context.Object)); // Assert Assert.IsType(exception.InnerException); } #if !NET452 [Theory] [MemberData(nameof(GetSchedulers))] public void Run_FlowsAsyncLocal_ThroughFilters_AndSynchronousBackgroundJobMethod(TaskScheduler scheduler) { // Arrange var id = Guid.NewGuid(); _filters.Add(new AsyncLocalFilter(id)); _context.BackgroundJob.Job = Job.FromExpression(() => AsyncLocalSync()); var performer = CreatePerformer(CreateInnerPerformer(scheduler)); // Act var result = performer.Perform(_context.Object); // Assert Assert.Equal(id, result); } [Theory] [MemberData(nameof(GetSchedulers))] public void Run_FlowsAsyncLocal_ThroughFilters_AndSimpleAsynchronousBackgroundJobMethod(TaskScheduler scheduler) { // Arrange var id = Guid.NewGuid(); _filters.Add(new AsyncLocalFilter(id)); _context.BackgroundJob.Job = Job.FromExpression(() => AsyncLocalSimpleAsync()); var performer = CreatePerformer(CreateInnerPerformer(scheduler)); // Act var result = performer.Perform(_context.Object); // Assert Assert.Equal(id, result); } [Theory] [MemberData(nameof(GetSchedulers))] public void Run_FlowsAsyncLocal_ThroughFilters_AndAsyncAwaitAsynchronousBackgroundJobMethod(TaskScheduler scheduler) { // Arrange var id = Guid.NewGuid(); _filters.Add(new AsyncLocalFilter(id)); _context.BackgroundJob.Job = Job.FromExpression(() => AsyncLocalAsyncAwait()); var performer = CreatePerformer(CreateInnerPerformer(scheduler)); // Act var result = performer.Perform(_context.Object); // Assert Assert.Equal(id, result); } [Theory] [MemberData(nameof(GetSchedulers))] public void Run_FlowsAsyncLocal_ThroughFilters_AndAsyncAwaitContinuationAsynchronousBackgroundJobMethod(TaskScheduler scheduler) { // Arrange var id = Guid.NewGuid(); _filters.Add(new AsyncLocalFilter(id)); _context.BackgroundJob.Job = Job.FromExpression(() => AsyncLocalAsyncAwaitContinuation()); var performer = CreatePerformer(CreateInnerPerformer(scheduler)); // Act var result = performer.Perform(_context.Object); // Assert Assert.Equal(id, result); } private static CoreBackgroundJobPerformer CreateInnerPerformer(TaskScheduler taskScheduler) { return new CoreBackgroundJobPerformer(new JobActivator(), taskScheduler); } public static IEnumerable GetSchedulers() { yield return new object[] { null }; yield return new object[] { TaskScheduler.Default }; yield return new object[] { new Hangfire.Processing.BackgroundTaskScheduler(threadCount: 1) }; } #endif private BackgroundJobPerformer CreatePerformer(IBackgroundJobPerformer inner = null) { return new BackgroundJobPerformer(_filterProvider.Object, inner ?? _innerPerformer.Object); } private Mock CreateFilter() where T : class { var filter = new Mock(); _filters.Add(filter.Object); return filter; } #if !NET452 private static readonly AsyncLocal Identifier = new AsyncLocal(); [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")] public static Guid AsyncLocalSync() { return Identifier.Value; } [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static Task AsyncLocalSimpleAsync() { return Task.FromResult(Identifier.Value); } [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public static async Task AsyncLocalAsyncAwait() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { return Identifier.Value; } [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static async Task AsyncLocalAsyncAwaitContinuation() { await Task.Yield(); return Identifier.Value; } private sealed class AsyncLocalFilter : IServerFilter { private readonly Guid _identifier; public AsyncLocalFilter(Guid identifier) { _identifier = identifier; } public void OnPerforming(PerformingContext context) { Identifier.Value = _identifier; } public void OnPerformed(PerformedContext context) { Assert.Equal(_identifier, Identifier.Value); } } #endif } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/BackgroundJobServerOptionsFacts.cs ================================================ using System; using System.Threading; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.Server { public class BackgroundJobServerOptionsFacts { [Fact] public void Ctor_InitializeProperties_WithCorrectValues() { var options = CreateOptions(); Assert.Equal(Math.Min(Environment.ProcessorCount * 5, 20), options.WorkerCount); Assert.Equal(EnqueuedState.DefaultQueue, options.Queues[0]); Assert.Equal(TimeSpan.FromSeconds(15), options.ShutdownTimeout); Assert.Equal(TimeSpan.FromSeconds(15), options.SchedulePollingInterval); Assert.Equal(TimeSpan.FromMinutes(5), options.ServerTimeout); Assert.Equal(TimeSpan.FromMinutes(5), options.ServerCheckInterval); Assert.Equal(TimeSpan.FromSeconds(30), options.HeartbeatInterval); } [Fact] public void WorkerCount_ThrowsAnException_WhenValueIsEqualToZero() { var options = CreateOptions(); Assert.Throws( () => options.WorkerCount = 0); } [Fact] public void WorkerCount_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.WorkerCount = -1); } [Fact] public void Queues_ThrowsAnException_WhenValueIsNull() { var options = CreateOptions(); Assert.Throws( () => options.Queues = null); } [Fact] public void Queues_ThrowsAnException_WhenGivenArrayIsEmpty() { var options = CreateOptions(); Assert.Throws( () => options.Queues = new string[0]); } [Fact] public void ServerTimeout_ThrowsAnException_WhenValueIsTooLarge() { var options = CreateOptions(); Assert.Throws( () => options.ServerTimeout = TimeSpan.FromHours(25)); } [Fact] public void ServerTimeout_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.ServerTimeout = TimeSpan.FromHours(-1)); } [Fact] public void ServerTimeout_ThrowsAnException_WhenValueIsInfinite() { var options = CreateOptions(); Assert.Throws( () => options.ServerTimeout = Timeout.InfiniteTimeSpan); } [Fact] public void ServerCheckInterval_ThrowsAnException_WhenValueIsTooLarge() { var options = CreateOptions(); Assert.Throws( () => options.ServerCheckInterval = TimeSpan.FromHours(25)); } [Fact] public void ServerCheckInterval_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.ServerCheckInterval = TimeSpan.FromHours(-1)); } [Fact] public void ServerCheckInterval_ThrowsAnException_WhenValueIsInfinite() { var options = CreateOptions(); Assert.Throws( () => options.ServerCheckInterval = Timeout.InfiniteTimeSpan); } [Fact] public void HeartbeatInterval_ThrowsAnException_WhenValueIsTooLarge() { var options = CreateOptions(); Assert.Throws( () => options.HeartbeatInterval = TimeSpan.FromHours(25)); } [Fact] public void HeartbeatInterval_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.HeartbeatInterval = TimeSpan.FromHours(-1)); } [Fact] public void HeartbeatInterval_ThrowsAnException_WhenValueIsInfinite() { var options = CreateOptions(); Assert.Throws( () => options.HeartbeatInterval = Timeout.InfiniteTimeSpan); } [Fact] public void ShutdownTimeout_ThrowsAnException_WhenValueIsTooLarge() { var options = CreateOptions(); Assert.Throws( () => options.ShutdownTimeout = TimeSpan.FromMilliseconds((double)Int32.MaxValue+1)); } [Fact] public void ShutdownTimeout_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.ShutdownTimeout = TimeSpan.FromHours(-1)); } [Fact] public void ShutdownTimeout_DoesNotThrowAnException_WhenValueIsInfinite() { var options = CreateOptions(); options.ShutdownTimeout = Timeout.InfiniteTimeSpan; Assert.Equal(Timeout.InfiniteTimeSpan, options.ShutdownTimeout); } [Fact] public void SchedulePollingInterval_ThrowsAnException_WhenValueIsTooLarge() { var options = CreateOptions(); Assert.Throws( () => options.SchedulePollingInterval = TimeSpan.FromMilliseconds((double)Int32.MaxValue + 1)); } [Fact] public void SchedulePollingInterval_ThrowsAnException_WhenValueIsNegative() { var options = CreateOptions(); Assert.Throws( () => options.SchedulePollingInterval = TimeSpan.FromHours(-1)); } [Fact] public void SchedulePollingInterval_ThrowsAnException_WhenValueIsInfinite() { var options = CreateOptions(); Assert.Throws( () => options.SchedulePollingInterval = Timeout.InfiniteTimeSpan); } private static BackgroundJobServerOptions CreateOptions() { return new BackgroundJobServerOptions(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/BackgroundProcessContextFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Hangfire.Server; using Moq; using Xunit; #pragma warning disable 618 // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Server { public class BackgroundProcessContextFacts { private readonly string _serverId = "server"; private readonly Mock _storage; private readonly CancellationTokenSource _cts; private readonly Dictionary _properties; public BackgroundProcessContextFacts() { _storage = new Mock(); _properties = new Dictionary {{"key", "value"}}; _cts = new CancellationTokenSource(); } [Fact] public void Ctor_ThrowsAnException_WhenServerIdIsNull() { var exception = Assert.Throws( () => new BackgroundProcessContext(null, _storage.Object, _properties, _cts.Token)); Assert.Equal("serverId", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new BackgroundProcessContext(_serverId, null, _properties, _cts.Token)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenPropertiesArgumentIsNull() { var exception = Assert.Throws( () => new BackgroundProcessContext(_serverId, _storage.Object, null, _cts.Token)); Assert.Equal("properties", exception.ParamName); } [Fact] public void Ctor_CorrectlyInitializes_AllTheProperties() { var context = new BackgroundProcessContext(_serverId, _storage.Object, _properties, _cts.Token); Assert.Equal(_serverId, context.ServerId); Assert.True(_properties.SequenceEqual(context.Properties)); Assert.Same(_storage.Object, context.Storage); Assert.Equal(_cts.Token, context.CancellationToken); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/BackgroundProcessingServerFacts.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using Hangfire.Server; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Server { public class BackgroundProcessingServerFacts { private readonly string[] _queues = { "queue" }; private readonly Mock _storage; private readonly List _processes; private readonly Dictionary _properties; private readonly Mock _connection; public BackgroundProcessingServerFacts() { _storage = new Mock(); _processes = new List(); _properties = new Dictionary { { "Queues", _queues } }; _connection = new Mock(); _storage.Setup(x => x.GetConnection()).Returns(_connection.Object); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new BackgroundProcessingServer(null, _processes, _properties)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenProcessesArgumentIsNull() { var exception = Assert.Throws( () => new BackgroundProcessingServer(_storage.Object, null, _properties)); Assert.Equal("processes", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenPropertiesArgumentIsNull() { var exception = Assert.Throws( () => new BackgroundProcessingServer(_storage.Object, _processes, null)); Assert.Equal("properties", exception.ParamName); } [Fact] public void Ctor_AnnouncesTheServer_AndRemovesIt() { using (CreateServer()) { Thread.Sleep(50); } _connection.Verify(x => x.AnnounceServer( It.IsNotNull(), It.Is(y => y.Queues == _queues))); _connection.Verify(x => x.RemoveServer(It.IsNotNull())); } [Fact] public void Execute_StartsAllTheProcesses_InLoop_AndWaitsForThem() { // Arrange var component1Countdown = new CountdownEvent(5); var component2Countdown = new CountdownEvent(5); var component1 = CreateProcessMock(); component1.Setup(x => x.Execute(It.IsAny())).Callback(() => { component1Countdown.Signal(); }); var component2 = CreateProcessMock(); component2.Setup(x => x.Execute(It.IsAny())).Callback(() => { component2Countdown.Signal(); }); // Act using (CreateServer()) { WaitHandle.WaitAll(new[] { component1Countdown.WaitHandle, component2Countdown.WaitHandle }); } // Assert component1.Verify(x => x.Execute(It.IsAny()), Times.AtLeast(5)); component2.Verify(x => x.Execute(It.IsNotNull()), Times.AtLeast(5)); } private BackgroundProcessingServer CreateServer() { return new BackgroundProcessingServer(_storage.Object, _processes, _properties); } private Mock CreateProcessMock() where T : class, IBackgroundProcess { var mock = new Mock(); _processes.Add(mock.Object); return mock; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/CoreBackgroundJobPerformerFacts.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Hangfire.Common; using Hangfire.Core.Tests.Common; using Hangfire.Server; using Moq; using Xunit; namespace Hangfire.Core.Tests.Server { public class CoreBackgroundJobPerformerFacts : IDisposable { private readonly Mock _activator = new Mock() { CallBase = true }; private readonly PerformContextMock _context = new PerformContextMock(); private static readonly DateTime SomeDateTime = new DateTime(2014, 5, 30, 12, 0, 0); private static bool _methodInvoked; private static bool _disposed; [Fact] public void Ctor_ThrowsAnException_WhenActivatorIsNull() { var exception = Assert.Throws( // ReSharper disable once AssignNullToNotNullAttribute () => new CoreBackgroundJobPerformer(null, null)); Assert.Equal("activator", exception.ParamName); } [Fact] public void Ctor_DoesNotThrowAnException_WhenTaskSchedulerIsNull() { var performer = new CoreBackgroundJobPerformer(_activator.Object, null); Assert.NotNull(performer); } [Fact, StaticLock] public void Perform_CanInvokeStaticMethods() { _methodInvoked = false; _context.BackgroundJob.Job = Job.FromExpression(() => StaticMethod()); var performer = CreatePerformer(); performer.Perform(_context.Object); Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_CanInvokeInstanceMethods() { _methodInvoked = false; _context.BackgroundJob.Job = Job.FromExpression(x => x.InstanceMethod()); var performer = CreatePerformer(); performer.Perform(_context.Object); Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_ActivatesJob_WithinAScope() { var performer = CreatePerformer(); _context.BackgroundJob.Job = Job.FromExpression(x => x.InstanceMethod()); performer.Perform(_context.Object); _activator.Verify(x => x.BeginScope(It.IsNotNull()), Times.Once); } [Fact, StaticLock] public void Perform_DisposesDisposableInstance_AfterPerformance() { _disposed = false; _context.BackgroundJob.Job = Job.FromExpression(x => x.Method()); var performer = CreatePerformer(); performer.Perform(_context.Object); Assert.True(_disposed); } [Fact, StaticLock] public void Perform_PassesArguments_ToACallingMethod() { // Arrange _methodInvoked = false; _context.BackgroundJob.Job = Job.FromExpression(() => MethodWithArguments("hello", 5)); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert - see the `MethodWithArguments` method. Assert.True(_methodInvoked); } #if !NETCOREAPP1_0 [Fact, StaticLock] public void Perform_PassesCorrectDateTime_IfItWasSerialized_UsingTypeConverter() { // Arrange _methodInvoked = false; var typeConverter = TypeDescriptor.GetConverter(typeof(DateTime)); var convertedDate = typeConverter.ConvertToInvariantString(SomeDateTime); var type = typeof(CoreBackgroundJobPerformerFacts); var method = type.GetMethod("MethodWithDateTimeArgument"); #pragma warning disable CS0618 // Type or member is obsolete _context.BackgroundJob.Job = new Job(type, method, new [] { convertedDate }); #pragma warning restore CS0618 // Type or member is obsolete var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert - see also the `MethodWithDateTimeArgument` method. Assert.True(_methodInvoked); } #endif [Fact, StaticLock] public void Perform_PassesCorrectDateTime_IfItWasSerialized_UsingOldFormat() { // Arrange _methodInvoked = false; var convertedDate = SomeDateTime.ToString("MM/dd/yyyy HH:mm:ss.ffff"); var type = typeof(CoreBackgroundJobPerformerFacts); var method = type.GetMethod("MethodWithDateTimeArgument"); #pragma warning disable CS0618 // Type or member is obsolete _context.BackgroundJob.Job = new Job(type, method, new [] { convertedDate }); #pragma warning restore CS0618 // Type or member is obsolete var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert - see also the `MethodWithDateTimeArgument` method. Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_PassesCorrectDateTimeArguments() { // Arrange _methodInvoked = false; _context.BackgroundJob.Job = Job.FromExpression(() => MethodWithDateTimeArgument(SomeDateTime)); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert - see also the `MethodWithDateTimeArgument` method. Assert.True(_methodInvoked); } [Fact, StaticLock] public void Perform_WorksCorrectly_WithNullValues() { // Arrange _methodInvoked = false; _context.BackgroundJob.Job = Job.FromExpression(() => NullArgumentMethod(null)); var performer = CreatePerformer(); // Act performer.Perform(_context.Object); // Assert - see also `NullArgumentMethod` method. Assert.True(_methodInvoked); } [Fact] public void Perform_ThrowsException_WhenActivatorThrowsAnException() { // Arrange var exception = new InvalidOperationException(); _activator.Setup(x => x.ActivateJob(It.IsAny())).Throws(exception); _context.BackgroundJob.Job = Job.FromExpression(() => InstanceMethod()); var performer = CreatePerformer(); // Act Assert.Throws( () => performer.Perform(_context.Object)); } [Fact] public void Perform_ThrowsPerformanceException_WhenActivatorReturnsNull() { _activator.Setup(x => x.ActivateJob(It.IsNotNull())).Returns(null); _context.BackgroundJob.Job = Job.FromExpression(() => InstanceMethod()); var performer = CreatePerformer(); Assert.Throws( () => performer.Perform(_context.Object)); } [Fact] public void Perform_ThrowsPerformanceException_OnArgumentsDeserializationFailure() { var type = typeof(JobFacts); var method = type.GetMethod("MethodWithDateTimeArgument"); _context.BackgroundJob.Job = new Job(type, method, "sdfa"); var performer = CreatePerformer(); var exception = Assert.Throws( () => performer.Perform(_context.Object)); Assert.NotNull(exception.InnerException); } [Fact, StaticLock] public void Perform_ThrowsPerformanceException_OnDisposalFailure() { _methodInvoked = false; _context.BackgroundJob.Job = Job.FromExpression(x => x.Method()); var performer = CreatePerformer(); Assert.Throws( () => performer.Perform(_context.Object)); Assert.True(_methodInvoked); } [Fact] public void Perform_ThrowsPerformanceException_WithUnwrappedInnerException() { _context.BackgroundJob.Job = Job.FromExpression(() => ExceptionMethod()); var performer = CreatePerformer(); var thrownException = Assert.Throws( () => performer.Perform(_context.Object)); Assert.IsType(thrownException.InnerException); Assert.Equal("exception", thrownException.InnerException.Message); } [Fact] public void Run_ThrowsPerformanceException_WithUnwrappedInnerException_ForTasks() { _context.BackgroundJob.Job = Job.FromExpression(() => TaskExceptionMethod()); var performer = CreatePerformer(); var thrownException = Assert.Throws( () => performer.Perform(_context.Object)); Assert.IsType(thrownException.InnerException); Assert.Equal("exception", thrownException.InnerException.Message); } [Fact] public void Perform_ThrowsPerformanceException_WhenMethodThrownTaskCanceledException() { _context.BackgroundJob.Job = Job.FromExpression(() => TaskCanceledExceptionMethod()); var performer = CreatePerformer(); var thrownException = Assert.Throws( () => performer.Perform(_context.Object)); Assert.IsType(thrownException.InnerException); } [Fact] public void Perform_RethrowsOperationCanceledException_WhenShutdownTokenIsCanceled() { // Arrange _context.BackgroundJob.Job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(new CancellationToken(true)); _context.CancellationToken.Setup(x => x.ThrowIfCancellationRequested()).Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws(() => performer.Perform(_context.Object)); } [Fact] public void Run_RethrowsTaskCanceledException_WhenShutdownTokenIsCanceled() { // Arrange _context.BackgroundJob.Job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(new CancellationToken(true)); _context.CancellationToken.Setup(x => x.ThrowIfCancellationRequested()).Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws(() => performer.Perform(_context.Object)); } [Fact] public void Run_RethrowsJobAbortedException() { // Arrange _context.BackgroundJob.Job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(new CancellationToken(true)); _context.CancellationToken.Setup(x => x.ThrowIfCancellationRequested()).Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws(() => performer.Perform(_context.Object)); } [Fact] public void ThrowsJobPerformanceException_DoesInclude_JobId() { // Arrange _context.BackgroundJob.Job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(CancellationToken.None); _context.CancellationToken.Setup(x => x.ThrowIfCancellationRequested()).Throws(); var performer = CreatePerformer(); // Act & Assert var exception = Assert.Throws(() => performer.Perform(_context.Object)); Assert.Equal(_context.BackgroundJob.Id, exception.JobId); } [Fact] public void Run_ThrowsJobPerformanceException_InsteadOfOperationCanceled_WhenShutdownWasNOTInitiated() { // Arrange _context.BackgroundJob.Job = Job.FromExpression(() => CancelableJob(JobCancellationToken.Null)); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(CancellationToken.None); _context.CancellationToken.Setup(x => x.ThrowIfCancellationRequested()).Throws(); var performer = CreatePerformer(); // Act & Assert Assert.Throws(() => performer.Perform(_context.Object)); } [Fact] public void Run_PassesStandardCancellationToken_IfThereIsCancellationTokenParameter() { // Arrange _context.BackgroundJob.Job = Job.FromExpression(() => CancelableJob(default(CancellationToken))); var source = new CancellationTokenSource(); _context.CancellationToken.Setup(x => x.ShutdownToken).Returns(source.Token); var performer = CreatePerformer(); // Act & Assert source.Cancel(); Assert.Throws( () => performer.Perform(_context.Object)); } [Fact] public void Perform_ReturnsValue_WhenCallingFunctionReturningValue() { _context.BackgroundJob.Job = Job.FromExpression(x => x.FunctionReturningValue()); var performer = CreatePerformer(); var result = performer.Perform(_context.Object); Assert.Equal("Return value", result); } [Fact] public void Run_DoesNotReturnValue_WhenCallingFunctionReturningPlainTask() { _context.BackgroundJob.Job = Job.FromExpression(x => x.FunctionReturningTask()); var performer = CreatePerformer(); var result = performer.Perform(_context.Object); Assert.Null(result); } [Fact] public void Run_DoesNotReturnValue_WhenCallingFunctionReturningValueTask() { _context.BackgroundJob.Job = Job.FromExpression(x => x.FunctionReturningValueTask()); var performer = CreatePerformer(); var result = performer.Perform(_context.Object); Assert.Null(result); } [Theory] [InlineData(true)] [InlineData(false)] public void Run_ReturnsTaskResult_WhenCallingFunctionReturningGenericTask(bool continueOnCapturedContext) { _context.BackgroundJob.Job = Job.FromExpression(x => x.FunctionReturningTaskResultingInString(continueOnCapturedContext)); var performer = CreatePerformer(); var result = performer.Perform(_context.Object); Assert.Equal("Return value", result); } [Theory] [InlineData(true)] [InlineData(false)] public void Run_ReturnsTaskResult_WhenCallingFunctionReturningValueTask(bool continueOnCapturedContext) { _context.BackgroundJob.Job = Job.FromExpression(x => x.FunctionReturningValueTaskResultingInString(continueOnCapturedContext)); var performer = CreatePerformer(); var result = performer.Perform(_context.Object); Assert.Equal("Return value", result); } [Fact] public void Perform_ExecutesAsyncMethod_AlwaysWithinTheSameThread() { SynchronizationContext.SetSynchronizationContext(null); _context.BackgroundJob.Job = Job.FromExpression(() => AsyncMethod(Thread.CurrentThread.ManagedThreadId)); var performer = CreatePerformer(); var result = performer.Perform(_context.Object); Assert.True((bool)result); } [Fact] public void Perform_ExecutesAsyncMethod_OnCustomScheduler_WhenItIsSet() { SynchronizationContext.SetSynchronizationContext(null); var scheduler = new MyCustomTaskScheduler(); _context.BackgroundJob.Job = Job.FromExpression(() => TaskExceptionMethod()); var performer = CreatePerformer(scheduler); Assert.Throws(() => performer.Perform(_context.Object)); Assert.True(scheduler.TasksPerformed > 1); } #if !NET452 private static readonly AsyncLocal AsyncLocal = new AsyncLocal(); [Fact] public void Perform_FlowsExistingAsyncLocal_WhenInvokingSynchronousMethod() { InvokeJobMethodWithFlowOfAsyncLocal(Job.FromExpression(() => CheckSynchronousAsyncLocal())); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void CheckSynchronousAsyncLocal() { Assert.Equal("world", AsyncLocal.Value); } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_FlowsExistingAsyncLocal_WhenInvokingSimpleAsyncMethod(TaskScheduler scheduler) { InvokeJobMethodWithFlowOfAsyncLocal(Job.FromExpression(() => CheckSimpleAsyncLocal()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static Task CheckSimpleAsyncLocal() { Assert.Equal("world", AsyncLocal.Value); return Task.CompletedTask; } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_FlowsExistingAsyncLocal_WhenInvokingAsyncMethodWithAwait(TaskScheduler scheduler) { InvokeJobMethodWithFlowOfAsyncLocal(Job.FromExpression(() => CheckAsyncAwaitAsyncLocal()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public static async Task CheckAsyncAwaitAsyncLocal() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { Assert.Equal("world", AsyncLocal.Value); } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_FlowsExistingAsyncLocal_WhenInvokingAsyncMethodAndSettingAsyncLocalAfterAwait(TaskScheduler scheduler) { InvokeJobMethodWithFlowOfAsyncLocal(Job.FromExpression(() => CheckAsyncLocalAfterAwait()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static async Task CheckAsyncLocalAfterAwait() { await Task.Yield(); Assert.Equal("world", AsyncLocal.Value); } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_FlowsExistingAsyncLocal_WhenInvokingAsyncMethodAndSettingAsyncLocalInTaskContinuation(TaskScheduler scheduler) { InvokeJobMethodWithFlowOfAsyncLocal(Job.FromExpression(() => CheckAsyncLocalInExplicitTaskContinuation()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static Task CheckAsyncLocalInExplicitTaskContinuation() { return Task.FromResult(true).ContinueWith(_ => { Assert.Equal("world", AsyncLocal.Value); }); } private void InvokeJobMethodWithFlowOfAsyncLocal(Job job, TaskScheduler scheduler = null) { AsyncLocal.Value = "world"; var parallelism = Math.Max(10, Environment.ProcessorCount); Parallel.For(0, parallelism, new ParallelOptions { MaxDegreeOfParallelism = parallelism }, _ => { _context.BackgroundJob.Job = job; var performer = CreatePerformer(scheduler); performer.Perform(_context.Object); Assert.Equal("world", AsyncLocal.Value); }); Assert.Equal("world", AsyncLocal.Value); } [Fact] public void Perform_DoesNotLeakAsyncLocal_WhenInvokingSynchronousMethod() { InvokeJobMethodAndAssertAsyncLocalIsNull(Job.FromExpression(() => SynchronousAsyncLocal())); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void SynchronousAsyncLocal() { Assert.Null(AsyncLocal.Value); AsyncLocal.Value = "hello"; } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_DoesNotLeakAsyncLocal_WhenInvokingSimpleAsyncMethod(TaskScheduler scheduler) { InvokeJobMethodAndAssertAsyncLocalIsNull(Job.FromExpression(() => SimpleAsyncLocal()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static Task SimpleAsyncLocal() { Assert.Null(AsyncLocal.Value); AsyncLocal.Value = "hello"; return Task.CompletedTask; } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_DoesNotLeakAsyncLocal_WhenInvokingAsyncMethodWithAwait(TaskScheduler scheduler) { InvokeJobMethodAndAssertAsyncLocalIsNull(Job.FromExpression(() => AsyncAwaitAsyncLocal()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public static async Task AsyncAwaitAsyncLocal() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { Assert.Null(AsyncLocal.Value); AsyncLocal.Value = "hello"; } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_DoesNotLeakAsyncLocal_WhenInvokingAsyncMethodAndSettingAsyncLocalAfterAwait(TaskScheduler scheduler) { InvokeJobMethodAndAssertAsyncLocalIsNull(Job.FromExpression(() => AsyncLocalSetAfterAwait()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static async Task AsyncLocalSetAfterAwait() { await Task.Yield(); Assert.Null(AsyncLocal.Value); AsyncLocal.Value = "hello"; } [Theory] [MemberData(nameof(GetSchedulers))] public void Perform_DoesNotLeakAsyncLocal_WhenInvokingAsyncMethodAndSettingAsyncLocalInTaskContinuation(TaskScheduler scheduler) { InvokeJobMethodAndAssertAsyncLocalIsNull(Job.FromExpression(() => AsyncLocalSetInExplicitTaskContinuation()), scheduler); } [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static Task AsyncLocalSetInExplicitTaskContinuation() { return Task.FromResult(true).ContinueWith(_ => { Assert.Null(AsyncLocal.Value); AsyncLocal.Value = "hello"; }); } private void InvokeJobMethodAndAssertAsyncLocalIsNull(Job job, TaskScheduler scheduler = null) { var parallelism = Math.Max(10, Environment.ProcessorCount); Parallel.For(0, parallelism, new ParallelOptions { MaxDegreeOfParallelism = parallelism }, _ => { _context.BackgroundJob.Job = job; var performer = CreatePerformer(scheduler); performer.Perform(_context.Object); Assert.Null(AsyncLocal.Value); }); Assert.Null(AsyncLocal.Value); } public static IEnumerable GetSchedulers() { yield return new object[] { null }; yield return new object[] { TaskScheduler.Default }; yield return new object[] { new Hangfire.Processing.BackgroundTaskScheduler(threadCount: 1) }; } #endif [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void InstanceMethod() { _methodInvoked = true; } [UsedImplicitly] public class Disposable : IDisposable { [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method() { _methodInvoked = true; } public void Dispose() { _disposed = true; GC.SuppressFinalize(this); } } [UsedImplicitly] public class BrokenDispose : IDisposable { [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method() { _methodInvoked = true; } public void Dispose() { GC.SuppressFinalize(this); throw new InvalidOperationException(); } } public void Dispose() { _disposed = true; GC.SuppressFinalize(this); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void NullArgumentMethod(string[] argument) { _methodInvoked = true; Assert.Null(argument); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void CancelableJob(IJobCancellationToken token) { token.ThrowIfCancellationRequested(); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void CancelableJob(CancellationToken token) { token.ThrowIfCancellationRequested(); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public void MethodWithDateTimeArgument(DateTime argument) { _methodInvoked = true; Assert.Equal(SomeDateTime, argument); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void StaticMethod() { _methodInvoked = true; } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public void MethodWithArguments(string stringArg, int intArg) { _methodInvoked = true; Assert.Equal("hello", stringArg); Assert.Equal(5, intArg); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void ExceptionMethod() { throw new InvalidOperationException("exception"); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static async Task TaskExceptionMethod() { await Task.Yield(); throw new InvalidOperationException("exception"); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void TaskCanceledExceptionMethod() { throw new TaskCanceledException(); } [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static async Task AsyncMethod(int threadId) { if (threadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("Start"); } await Task.Yield(); if (threadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("After Yield"); } await Task.Delay(1).ConfigureAwait(true); if (threadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("After Delay"); } Parallel.For(0, 4, new ParallelOptions { MaxDegreeOfParallelism = 4, TaskScheduler = TaskScheduler.Current }, i => { Thread.Sleep(TimeSpan.FromSeconds(1)); }); if (threadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("After Parallel.For"); } await Task.Delay(1).ConfigureAwait(false); #if NETCOREAPP1_0 if (threadId == Thread.CurrentThread.ManagedThreadId) #else if (!Thread.CurrentThread.IsThreadPoolThread) #endif { throw new InvalidOperationException("Not running on ThreadPool after ConfigureAwait(false)"); } return true; } private CoreBackgroundJobPerformer CreatePerformer(TaskScheduler taskScheduler = null) { return new CoreBackgroundJobPerformer(_activator.Object, taskScheduler); } private class MyCustomTaskScheduler : TaskScheduler { public int TasksPerformed { get; private set; } protected override void QueueTask(Task task) { TryExecuteTask(task); TasksPerformed++; } protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; } protected override IEnumerable GetScheduledTasks() { return Enumerable.Empty(); } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/DelayedJobSchedulerFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Hangfire.Server; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Server { public class DelayedJobSchedulerFacts { private const string JobId = "id"; private readonly Mock _connection; private readonly Mock _stateChanger; private readonly BackgroundProcessContextMock _context; private readonly Mock _transaction; private readonly Mock _distributedLock; private readonly List _schedule = new List(); public DelayedJobSchedulerFacts() { _context = new BackgroundProcessContextMock(); _connection = new Mock(); _context.Storage.Setup(x => x.GetConnection()).Returns(_connection.Object); _stateChanger = new Mock(); _transaction = new Mock(); _connection.Setup(x => x.CreateWriteTransaction()).Returns(_transaction.Object); _distributedLock = new Mock(); _connection .Setup(x => x.AcquireDistributedLock("locks:schedulepoller", It.IsAny())) .Returns(_distributedLock.Object); _connection .Setup(x => x.GetFirstByLowestScoreFromSet("schedule", 0, It.Is(time => time > 0))) .Returns(_schedule.FirstOrDefault); _connection .Setup(x => x.GetFirstByLowestScoreFromSet("schedule", 0, It.Is(time => time > 0), It.IsAny())) .Returns(_schedule.ToList); _stateChanger .Setup(x => x.ChangeState(It.IsNotNull())) .Callback(ctx => { if (!(ctx.NewState is ScheduledState)) _schedule.Remove(ctx.BackgroundJobId); }); _transaction .Setup(x => x.RemoveFromSet("schedule", It.IsNotNull())) .Callback((key, value) => _schedule.Remove(value)); } [Fact] public void Ctor_ThrowsAnException_WhenStateChangerIsNull() { var exception = Assert.Throws( () => new DelayedJobScheduler(Timeout.InfiniteTimeSpan, null)); Assert.Equal("stateChanger", exception.ParamName); } [Fact] public void Execute_MovesJobStateToEnqueued() { var scheduler = CreateScheduler(); _schedule.Add(JobId); scheduler.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == JobId && ctx.NewState is EnqueuedState && ctx.ExpectedStates.SequenceEqual(new[] { ScheduledState.StateName }) && ctx.DisableFilters == false))); _connection.Verify(x => x.Dispose()); } [Fact] public void Execute_EnqueuesJobIdDirectly_AndRemovesItFromSchedule_WhenTargetQueueIsEncodedIntoTheSetEntry() { // Arrange _schedule.Add("default:some-id"); _schedule.Add("critical:another-id"); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.AddToQueue("default", "some-id")); _transaction.Verify(x => x.RemoveFromSet("schedule", "default:some-id")); _transaction.Verify(x => x.AddToQueue("critical", "another-id")); _transaction.Verify(x => x.RemoveFromSet("schedule", "critical:another-id")); _transaction.Verify(x => x.Commit(), Times.Exactly(2)); _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Never); } [Fact] public void Execute_MovesJobStateToEnqueued_UsingBatching_WhenAvailable() { // Arrange EnableBatching(); _schedule.Add("job-1"); _schedule.Add("job-2"); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == "job-1" && ctx.NewState is EnqueuedState))); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == "job-2" && ctx.NewState is EnqueuedState))); } [Fact] public void Execute_WithBatching_EnqueuesJobIdDirectly_AndRemovesItFromSchedule_WhenTargetQueueIsEncodedIntoTheSetEntry() { // Arrange EnableBatching(); _schedule.Add("default:some-id"); _schedule.Add("critical:another-id"); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.AddToQueue("default", "some-id")); _transaction.Verify(x => x.AddToQueue("critical", "another-id")); _transaction.Verify(x => x.RemoveFromSet("schedule", "default:some-id")); _transaction.Verify(x => x.RemoveFromSet("schedule", "critical:another-id")); _transaction.Verify(x => x.Commit(), Times.Once); _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Never); } [Fact] public void Execute_DoesNotUseBatching_WhenConnectionMethod_ThrowsAnException() { // Arrange _connection .Setup(x => x.GetFirstByLowestScoreFromSet(It.IsAny(), It.IsAny(), It.IsAny(),It.IsAny())) .Throws(); _schedule.Add("job-1"); _connection .Setup(x => x.GetFirstByLowestScoreFromSet("schedule", 0, It.Is(time => time > 0))) .Returns(_schedule.FirstOrDefault); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == "job-1" && ctx.NewState is EnqueuedState))); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DoesNotCallStateChanger_IfThereAreNoJobsToEnqueue(bool batching) { if (batching) EnableBatching(); var scheduler = CreateScheduler(); scheduler.Execute(_context.Object); _stateChanger.Verify( x => x.ChangeState(It.IsAny()), Times.Never); } [Theory] [InlineData(false), InlineData(true)] public void Execute_RemovesAJobIdentifierFromTheSet_WhenStateChangeFails(bool batching) { if (batching) EnableBatching(); _stateChanger .Setup(x => x.ChangeState(It.IsAny())) .Returns(null); _schedule.Add(JobId); var scheduler = CreateScheduler(); scheduler.Execute(_context.Object); _transaction.Verify(x => x.RemoveFromSet("schedule", JobId)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_MovesJobToTheFailedState_WithFiltersDisabled_WhenStateChangerThrowsAnException(bool batching) { // Arrange if (batching) EnableBatching(); _schedule.Add(JobId); _stateChanger .Setup(x => x.ChangeState(It.Is(ctx => ctx.NewState is EnqueuedState))) .Throws(); var scheduler = CreateScheduler(); scheduler.RetryDelayFunc = _ => TimeSpan.FromMilliseconds(50); // Act scheduler.Execute(_context.Object); // Assert _stateChanger.Verify( x => x.ChangeState(It.Is(ctx => ctx.NewState is EnqueuedState)), Times.AtLeast(3)); _stateChanger.Verify( x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == JobId && ctx.NewState is FailedState && ((FailedState)ctx.NewState).Exception.GetType() == typeof(InvalidOperationException) && ctx.ExpectedStates.Contains(ScheduledState.StateName) && ctx.DisableFilters == true)), Times.Once); } [Theory] [InlineData(false), InlineData(true)] public void Execute_AbleToProcessFurtherJobs_WhenStateChangerThrowsAnException_ForPreviousOnes(bool batching) { // Arrange if (batching) EnableBatching(); _schedule.Add(JobId); _schedule.Add("AnotherId"); _stateChanger .Setup(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == JobId && ctx.NewState is ScheduledState))) .Throws(); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateChanger.Verify( x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == "AnotherId" && ctx.NewState is EnqueuedState)), Times.Once); } [Theory] [InlineData(false), InlineData(true)] public void Execute_ActsWithinADistributedLock(bool batching) { if (batching) EnableBatching(); var scheduler = CreateScheduler(); scheduler.Execute(_context.Object); _connection.Verify(x => x.AcquireDistributedLock(It.IsAny(), It.IsAny())); _distributedLock.Verify(x => x.Dispose()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DoesNotThrowDistributedLockTimeoutException(bool batching) { if (batching) EnableBatching(); _connection .Setup(x => x.AcquireDistributedLock("locks:schedulepoller", It.IsAny())) .Throws(new DistributedLockTimeoutException("locks:schedulepoller")); var scheduler = CreateScheduler(); scheduler.Execute(_context.Object); } [Theory] [InlineData(false), InlineData(true)] public void Execute_RemovesJobFromSchedule_WhenIdDoesNotExists(bool batching) { // Arrange if (batching) EnableBatching(); _schedule.Add(JobId); _connection.Setup(x => x.GetJobData(JobId)).Returns(null); _stateChanger .Setup(x => x.ChangeState(It.Is(ctx => ctx.NewState is EnqueuedState))) .Returns(null); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Once); _transaction.Verify(x => x.RemoveFromSet("schedule", JobId)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_RemovesJobFromSchedule_WhenJobIsNotInScheduledState(bool batching) { // Arrange if (batching) EnableBatching(); _schedule.Add(JobId); _connection.Setup(x => x.GetJobData(JobId)) .Returns(new JobData { State = SucceededState.StateName }); _stateChanger .Setup(x => x.ChangeState(It.Is(ctx => ctx.NewState is EnqueuedState))) .Returns(null); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.IsAny()), Times.Once); _transaction.Verify(x => x.RemoveFromSet("schedule", JobId)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DoesNotRemoveJobFromSchedule_WhenJobIsInTheScheduledState(bool batching) { // Arrange if (batching) EnableBatching(); _schedule.Add(JobId); _connection.Setup(x => x.GetJobData(JobId)) .Returns(new JobData { State = ScheduledState.StateName }); _stateChanger .SetupSequence(x => x.ChangeState(It.Is(ctx => ctx.NewState is EnqueuedState))) .Returns((IState)null) .Returns(() => { _schedule.Remove(JobId); return new EnqueuedState(); }); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.RemoveFromSet("schedule", JobId), Times.Never); _transaction.Verify(x => x.Commit(), Times.Never); } private DelayedJobScheduler CreateScheduler() { return new DelayedJobScheduler(TimeSpan.Zero, _stateChanger.Object); } private void EnableBatching() { _connection .Setup(x => x.GetFirstByLowestScoreFromSet(null, It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new ArgumentNullException("key")); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/PerformContextFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using Hangfire.Server; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Server { public class PerformContextFacts { private readonly Mock _storage; private readonly Mock _connection; private readonly Mock _cancellationToken; private readonly BackgroundJobMock _backgroundJob; public PerformContextFacts() { _storage = new Mock(); _connection = new Mock(); _backgroundJob = new BackgroundJobMock(); _cancellationToken = new Mock(); } [Fact] public void Ctor_DoesNotThrowAnException_WhenStorageIsNull() { var context = new PerformContext(null, _connection.Object, _backgroundJob.Object, _cancellationToken.Object); Assert.NotNull(context); } [Fact] public void Ctor_ThrowsAnException_WhenConnectionIsNull() { var exception = Assert.Throws( () => new PerformContext(_storage.Object, null, _backgroundJob.Object, _cancellationToken.Object)); Assert.Equal("connection", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenBackgroundJobIsNull() { var exception = Assert.Throws( () => new PerformContext(_storage.Object, _connection.Object, null, _cancellationToken.Object)); Assert.Equal("backgroundJob", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenCancellationTokenIsNull() { var exception = Assert.Throws( () => new PerformContext(_storage.Object, _connection.Object, _backgroundJob.Object, null)); Assert.Equal("cancellationToken", exception.ParamName); } [Fact] public void Ctor_CorrectlySets_AllInstanceProperties() { var context = CreateContext(); Assert.Same(_storage.Object, context.Storage); Assert.Equal(_backgroundJob.Object, context.BackgroundJob); Assert.NotNull(context.Items); Assert.Same(_connection.Object, context.Connection); Assert.Same(_cancellationToken.Object, context.CancellationToken); } [Fact] public void CopyCtor_ThrowsAnException_WhenContextIsNull() { Assert.Throws( () => new PerformContext(null)); } [Fact] public void CopyCtor_CopiesAllPropertyValues() { var context = CreateContext(); var contextCopy = new PerformContext(context); Assert.Same(context.Items, contextCopy.Items); Assert.Same(context.Storage, contextCopy.Storage); Assert.Same(context.Connection, contextCopy.Connection); Assert.Same(context.BackgroundJob, contextCopy.BackgroundJob); Assert.Same(context.CancellationToken, contextCopy.CancellationToken); } [Fact] public void SetJobParameter_ThrowsAnException_WhenParameterNameIsNullOrEmpty() { var context = CreateContext(); var exception = Assert.Throws( () => context.SetJobParameter(null, null)); Assert.Equal("name", exception.ParamName); } [Fact] public void SetJobParameter_ConvertsValueToJson_AndSetsItUsingConnection() { var context = CreateContext(); context.SetJobParameter("name", "value"); _connection.Verify(x => x.SetJobParameter(_backgroundJob.Id, "name", "\"value\"")); } [Fact] public void GetJobParameter_ThrowsAnException_WhenNameIsNullOrEmpty() { var context = CreateContext(); Assert.Throws( () => context.GetJobParameter(null)); } [Fact] public void GetJobParameter_ThrowsAnException_WhenParameterCouldNotBeDeserialized() { _connection.Setup(x => x.GetJobParameter(_backgroundJob.Id, "name")).Returns("value"); var context = CreateContext(); Assert.Throws( () => context.GetJobParameter("name")); } private PerformContext CreateContext() { return new PerformContext( _storage.Object, _connection.Object, _backgroundJob.Object, _cancellationToken.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/RecurringJobSchedulerFacts.cs ================================================ extern alias ReferencedCronos; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using ReferencedCronos::Cronos; using Hangfire.Client; using Hangfire.Common; using Hangfire.Server; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.Server { public class RecurringJobSchedulerFacts { private const string RecurringJobId = "recurring-job-id"; private readonly Mock _connection; private readonly Mock _transaction; private readonly Dictionary _recurringJob; private Func _nowInstantFactory; private readonly Mock _timeZoneResolver; private readonly BackgroundProcessContextMock _context; private readonly Mock _factory; private readonly Mock _stateMachine; private readonly BackgroundJobMock _backgroundJobMock; private static readonly string _expressionString = "* * * * *"; private static readonly TimeSpan _delay = TimeSpan.FromMilliseconds(1); private readonly CronExpression _cronExpression = CronExpression.Parse(_expressionString); private readonly DateTime _nowInstant = new DateTime(2017, 03, 30, 15, 30, 0, DateTimeKind.Utc); private readonly DateTime _nextInstant; private readonly List _schedule = new List { RecurringJobId }; public RecurringJobSchedulerFacts() { _context = new BackgroundProcessContextMock(); // Setting up the successful path var timeZone = TimeZoneInfo.Local; _nowInstantFactory = () => _nowInstant; _timeZoneResolver = new Mock(); _timeZoneResolver.Setup(x => x.GetTimeZoneById(It.IsAny())).Throws(); _timeZoneResolver.Setup(x => x.GetTimeZoneById(timeZone.Id)).Returns(timeZone); _timeZoneResolver.Setup(x => x.GetTimeZoneById("UTC")).Returns(TimeZoneInfo.Utc); // ReSharper disable once PossibleInvalidOperationException _nextInstant = _cronExpression.GetNextOccurrence(_nowInstant, timeZone).Value; _recurringJob = new Dictionary { { "Cron", _expressionString }, { "Job", InvocationData.SerializeJob(Job.FromExpression(() => Console.WriteLine())).SerializePayload() }, { "TimeZoneId", timeZone.Id } }; _connection = new Mock(); _context.Storage.Setup(x => x.GetConnection()).Returns(_connection.Object); _connection.Setup(x => x.GetFirstByLowestScoreFromSet("recurring-jobs", 0, JobHelper.ToTimestamp(_nowInstant))) .Returns(() => _schedule.FirstOrDefault()); _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{RecurringJobId}")) .Returns(_recurringJob); _connection.SetupSequence(x => x.GetFirstByLowestScoreFromSet("recurring-jobs", 0, JobHelper.ToTimestamp(_nowInstant), It.IsAny())) .Returns(() => _schedule.ToList()); _transaction = new Mock(); _transaction .Setup(x => x.RemoveFromSet("recurring-jobs", It.IsNotNull())) .Callback((key, value) => _schedule.Remove(value)); _transaction .Setup(x => x.AddToSet("recurring-jobs", It.IsNotNull(), It.IsAny())) .Callback((key, value, score) => _schedule.Remove(value)); _connection.Setup(x => x.CreateWriteTransaction()).Returns(_transaction.Object); _backgroundJobMock = new BackgroundJobMock(); _factory = new Mock(); _factory.Setup(x => x.Create(It.IsAny())).Returns(_backgroundJobMock.Object); _stateMachine = new Mock(); _factory.SetupGet(x => x.StateMachine).Returns(_stateMachine.Object); } [Fact] public void Ctor_ThrowsAnException_WhenJobFactoryIsNull() { var exception = Assert.Throws( // ReSharper disable once AssignNullToNotNullAttribute () => new RecurringJobScheduler(null, _delay, _timeZoneResolver.Object, _nowInstantFactory)); Assert.Equal("factory", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenTimeZoneResolverIsNull() { var exception = Assert.Throws( // ReSharper disable once AssignNullToNotNullAttribute () => new RecurringJobScheduler(_factory.Object, _delay, null, _nowInstantFactory)); Assert.Equal("timeZoneResolver", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenNowInstantFactoryIsNull() { var exception = Assert.Throws( // ReSharper disable once AssignNullToNotNullAttribute () => new RecurringJobScheduler(_factory.Object, _delay, _timeZoneResolver.Object, null)); Assert.Equal("nowFactory", exception.ParamName); } [Fact] public void Execute_ThrowsAnException_WhenContextIsNull() { var scheduler = CreateScheduler(); // ReSharper disable once AssignNullToNotNullAttribute var exception = Assert.Throws(() => scheduler.Execute(null)); Assert.Equal("context", exception.ParamName); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_EnqueuesAJob_WhenItIsTimeToRunIt(bool useJobStorageConnection) { SetupConnection(useJobStorageConnection); var scheduler = CreateScheduler(); scheduler.Execute(_context.Object); _factory.Verify(x => x.Create(It.IsNotNull())); _stateMachine.Verify(x => x.ApplyState(It.Is(ctx => ctx.NewState is EnqueuedState))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_DoesNotHandleRecurringJobs_CreatedByNewerVersion(bool useJobStorageConnection) { SetupConnection(useJobStorageConnection); _recurringJob["V"] = "3"; var scheduler = CreateScheduler(); scheduler.Execute(_context.Object); _factory.Verify(x => x.Create(It.IsAny()), Times.Never); } [Theory] [InlineData(false), InlineData(true)] public void Execute_ReschedulesRecurringJobs_WithUnsupportedVersions_WhenSomeRetriesLeft(bool batching) { // Arrange SetupConnection(batching); _recurringJob["V"] = "3"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict["RetryAttempt"] == "1"))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant + _delay))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DisablesRecurringJobs_WithUnsupportedVersions_WhenRetryAttemptsExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["V"] = "3"; _recurringJob["RetryAttempt"] = "10"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict["Error"].Contains("supported version")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_EnqueuesAJobToAGivenQueue_WhenItIsTimeToRunIt(bool useJobStorageConnection) { SetupConnection(useJobStorageConnection); _recurringJob["Queue"] = "critical"; var scheduler = CreateScheduler(); scheduler.Execute(_context.Object); _stateMachine.Verify(x => x.ApplyState( It.Is(ctx => ((EnqueuedState)ctx.NewState).Queue == "critical"))); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_UpdatesRecurringJobParameters_OnCompletion(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert var jobKey = $"recurring-job:{RecurringJobId}"; _transaction.Verify(x => x.SetRangeInHash( jobKey, It.Is>(rj => rj.ContainsKey("LastJobId") && rj["LastJobId"] == _backgroundJobMock.Id))); _transaction.Verify(x => x.SetRangeInHash( jobKey, It.Is>(rj => rj.ContainsKey("LastExecution") && rj["LastExecution"] == JobHelper.SerializeDateTime(_nowInstant)))); _transaction.Verify(x => x.SetRangeInHash( jobKey, It.Is>(rj => rj.ContainsKey("NextExecution") && rj["NextExecution"] == JobHelper.SerializeDateTime(_nextInstant)))); _transaction.Verify(x => x.SetRangeInHash( jobKey, It.Is>(rj => !rj.ContainsKey("RetryAttempt")))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_DoesNotUpdateRetryAttempt_WhenItWasNotModified(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant); _recurringJob["V"] = "2"; _recurringJob["RetryAttempt"] = "0"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(rj => !rj.ContainsKey("RetryAttempt")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddMinutes(1)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_DoesNotEnqueueRecurringJob_AndDoesNotUpdateIt_ButNextExecution_WhenItIsNotATimeToRunIt( bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var scheduler = CreateScheduler(_nowInstant); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(rj => rj.ContainsKey("NextExecution") && rj["NextExecution"] == JobHelper.SerializeDateTime(_nextInstant)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_TakesIntoConsideration_LastExecutionTime_ConvertedToLocalTimezone(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var time = _nowInstant; _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(time); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Never); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_RemovesRecurringJobFromSchedule_WhenHashDoesNotExist(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _schedule.Clear(); _schedule.Add("non-existing-job"); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.RemoveFromSet("recurring-jobs", "non-existing-job")); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_HandlesJobLoadException(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _recurringJob["Job"] = JobHelper.ToJson(new InvocationData("SomeType", "SomeMethod", "Parameters", "arguments")); var scheduler = CreateScheduler(); // Act & Assert does not throw scheduler.Execute(_context.Object); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_GetsInstance_InAGivenTimeZone(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var timeZoneId = PlatformHelper.IsRunningOnWindows() ? "Hawaiian Standard Time" : "Pacific/Honolulu"; var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); _recurringJob["TimeZoneId"] = timeZone.Id; var scheduler = CreateScheduler(); // Act & Assert does not throw scheduler.Execute(_context.Object); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_GetInstance_DoesNotCreateAJob_WhenGivenOneIsNotFound(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _recurringJob["TimeZoneId"] = "Some garbage"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Never); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_UsesGivenCreatedAtTime(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var createdAt = _nowInstant.AddHours(-3); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(createdAt); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); _factory.Verify(x => x.Create(It.IsAny()), Times.Once); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_DoesNotFixCreatedAtField_IfItExists(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(DateTime.UtcNow); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _connection.Verify( x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(rj => rj.ContainsKey("CreatedAt"))), Times.Never); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_FixedMissingCreatedAtField(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _recurringJob.Remove("CreatedAt"); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify( x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(rj => rj.ContainsKey("CreatedAt"))), Times.Once); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_UsesNextExecutionTime_WhenBothLastExecutionAndCreatedAtAreNotAvailable(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var nextExecution = _nowInstant.AddHours(-10); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(nextExecution); _recurringJob.Remove("CreatedAt"); _recurringJob.Remove("LastExecution"); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(rj => rj.ContainsKey("LastExecution") && rj["LastExecution"] == JobHelper.SerializeDateTime(_nowInstant)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_DoesNotThrowDistributedLockTimeoutException(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _connection .Setup(x => x.AcquireDistributedLock("recurring-jobs:lock", It.IsAny())) .Throws(new DistributedLockTimeoutException("recurring-jobs:lock")); var scheduler = CreateScheduler(); // Act & Assert (Does Not Throw) scheduler.Execute(_context.Object); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_DoesNotEnqueueRecurringJob_WhenItIsCorrectAndItWasNotTriggered(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddMinutes(1)); _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Never); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_AcquiresDistributedLock_ForEachRecurringJob(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _connection.Verify(x => x.AcquireDistributedLock("lock:recurring-job:recurring-job-id", It.IsAny())); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_SchedulesNextExecution_AfterCreatingAJob(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState(It.IsAny())); _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(rj => rj.ContainsKey("NextExecution") && rj["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddMinutes(1))))); _transaction.Verify(x => x.AddToSet( "recurring-jobs", "recurring-job-id", JobHelper.ToTimestamp(_nowInstant.AddMinutes(1)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_FixesNextExecution_WhenItsNotATimeToRunAJob(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(rj => rj.ContainsKey("NextExecution") && rj["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddMinutes(1))))); _transaction.Verify(x => x.AddToSet( "recurring-jobs", "recurring-job-id", JobHelper.ToTimestamp(_nowInstant.AddMinutes(1)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false)] [InlineData(true)] public void Execute_DoesNotCycleImmediately_WhenItCantDeserializeEverything(bool useJobStorageConnection) { // Arrange SetupConnection(useJobStorageConnection); _factory.Setup(x => x.Create(It.IsAny())).Throws(); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _connection.Verify(x => x.GetAllEntriesFromHash(It.IsAny()), Times.Once); } [Theory] [InlineData(false), InlineData(true)] public void Execute_UsesTimeZoneResolver_WhenCalculatingNextExecution(bool batching) { // Arrange SetupConnection(batching); var timeZone = TimeZoneInfo.FindSystemTimeZoneById(PlatformHelper.IsRunningOnWindows() ? "Hawaiian Standard Time" : "Pacific/Honolulu"); _timeZoneResolver .Setup(x => x.GetTimeZoneById(It.Is(id => id == "Hawaiian Standard Time" || id == "Pacific/Honolulu"))) .Returns(timeZone); // We are returning IANA time zone on Windows and Windows time zone on Linux. _recurringJob["Cron"] = "0 0 * * *"; _recurringJob["TimeZoneId"] = PlatformHelper.IsRunningOnWindows() ? "Pacific/Honolulu" : "Hawaiian Standard Time"; _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddHours(18).AddMinutes(30)); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{RecurringJobId}", It.Is>(dict => !dict.ContainsKey("NextExecution")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddHours(18).AddMinutes(30)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DoesNotScheduleRecurringJob_ToThePast(bool batching) { // Arrange SetupConnection(batching); _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddMinutes(-2)); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{RecurringJobId}", It.Is>(dict => dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddMinutes(1))))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddMinutes(1)))); _transaction.Verify(x => x.Commit(), Times.Once); } [Theory] [InlineData(false), InlineData(true)] public void Execute_CanHandleRecurringJob_WithCronThatNeverFires(bool batching) { // Arrange SetupConnection(batching); _recurringJob["Cron"] = "0 0 31 2 *"; _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _stateMachine.Verify( x => x.ApplyState(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash( $"recurring-job:{RecurringJobId}", It.Is>(dict => dict.ContainsKey("NextExecution") && dict["NextExecution"] == String.Empty))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1.0D)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_TriggersRecurringJobOnce_WithMissedScheduleByDefault(bool batching) { // Arrange SetupConnection(batching); _recurringJob["Cron"] = "0 * * * *"; _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); var scheduler = CreateScheduler(delay: TimeSpan.FromMilliseconds(100)); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.Is(ctx => (long)ctx.Parameters["Time"] == JobHelper.ToTimestamp(_nowInstant))), Times.Once); _stateMachine.Verify(x => x.ApplyState(It.IsNotNull()), Times.Once); _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{RecurringJobId}", It.Is>(dict => dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddMinutes(30)) && dict["LastExecution"] == JobHelper.SerializeDateTime(_nowInstant)))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddMinutes(30)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_TriggersRecurringJobMultipleTimes_WithMissedScheduleWhenStrictModeIsUsed(bool batching) { // Arrange SetupConnection(batching); _recurringJob["Cron"] = "0 * * * *"; _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddHours(-3)); _recurringJob["Misfire"] = MisfireHandlingMode.Strict.ToString("D"); var scheduler = CreateScheduler(delay: TimeSpan.FromMilliseconds(100)); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.Is(ctx => (long)ctx.Parameters["Time"] == JobHelper.ToTimestamp(_nowInstant.AddMinutes(-30)))), Times.Once); _factory.Verify(x => x.Create(It.Is(ctx => (long)ctx.Parameters["Time"] == JobHelper.ToTimestamp(_nowInstant.AddMinutes(-30).AddHours(-1)))), Times.Once); _factory.Verify(x => x.Create(It.Is(ctx => (long)ctx.Parameters["Time"] == JobHelper.ToTimestamp(_nowInstant.AddMinutes(-30).AddHours(-2)))), Times.Once); _stateMachine.Verify( x => x.ApplyState(It.IsNotNull()), Times.Exactly(3)); _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{RecurringJobId}", It.Is>(dict => dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddMinutes(30)) && dict["LastExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddMinutes(-30))))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddMinutes(30)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DoesNotTriggerRecurringJob_WithMissedScheduleWhenIgnorableModeIsUsed(bool batching) { // Arrange SetupConnection(batching); _recurringJob["Cron"] = "0 * * * *"; _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddHours(-3)); _recurringJob["Misfire"] = MisfireHandlingMode.Ignorable.ToString("D"); var scheduler = CreateScheduler(delay: TimeSpan.FromMilliseconds(100)); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _stateMachine.Verify(x => x.ApplyState(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{RecurringJobId}", It.Is>(dict => dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddMinutes(30)) && !dict.ContainsKey("LastExecution")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddMinutes(30)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_TriggersRecurringJob_WhenIgnorableModeIsUsed_AndErrorIsSlight(bool batching) { // Arrange SetupConnection(batching); _nowInstantFactory = () => _nowInstant.AddMilliseconds(123); _recurringJob["Cron"] = "30 * * * *"; _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddHours(-3)); _recurringJob["Misfire"] = MisfireHandlingMode.Ignorable.ToString("D"); var scheduler = CreateScheduler(delay: TimeSpan.FromMilliseconds(100)); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Once); _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{RecurringJobId}", It.Is>(dict => dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddHours(1)) && dict["LastExecution"] == JobHelper.SerializeDateTime(_nowInstant)))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddHours(1)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false, MisfireHandlingMode.Relaxed), InlineData(true, MisfireHandlingMode.Relaxed)] [InlineData(false, MisfireHandlingMode.Strict), InlineData(true, MisfireHandlingMode.Strict)] [InlineData(false, MisfireHandlingMode.Ignorable), InlineData(true, MisfireHandlingMode.Ignorable)] public void Execute_DoesNotMissCurrentExecution_ForAnyMisfireHandlingMode(bool batching, MisfireHandlingMode mode) { // Arrange SetupConnection(batching); _recurringJob["Cron"] = "30 * * * *"; _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddHours(-3)); _recurringJob["Misfire"] = mode.ToString("D"); var scheduler = CreateScheduler(delay: TimeSpan.FromMilliseconds(100)); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.Is(ctx => (long)ctx.Parameters["Time"] == JobHelper.ToTimestamp(_nowInstant))), Times.Once); _transaction.Verify(x => x.SetRangeInHash($"recurring-job:{RecurringJobId}", It.Is>(dict => dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.AddHours(1)) && dict["LastExecution"] == JobHelper.SerializeDateTime(_nowInstant)))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddHours(1)))); _transaction.Verify(x => x.Commit()); } [Fact] public void Execute_DoesNotUseBatchedMethod_WhenStorageConnectionThrowsAnException() { // Arrange SetupConnection(true); _connection .Setup(x => x.GetFirstByLowestScoreFromSet(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Throws(); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny())); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddMinutes(1)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_AlwaysUpdatesScoreForTheSetItem_EvenIfRecurringJobWasNotChanged(bool batching) { // Arrange SetupConnection(batching); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddMinutes(-1)); _recurringJob["LastExecution"] = JobHelper.SerializeDateTime(_nowInstant); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant.AddMinutes(1)); _recurringJob["V"] = "2"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.IsAny>>()), Times.Never); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.AddMinutes(1)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_UsesUtcTimeZone_WhenCorrespondingFieldIsNullOrEmpty(bool batching) { // Arrange SetupConnection(batching); _recurringJob["TimeZoneId"] = null; _recurringJob["Cron"] = "0 30 15 30 03 *"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny())); } [Theory] [InlineData(false), InlineData(true)] public void Execute_ReschedulesRecurringJob_WhenCronExpressionIsInvalid_AndRetryAttemptsAreNotExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["Cron"] = "some garbage"; _recurringJob["V"] = "2"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert Assert.True(_delay > TimeSpan.Zero); _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.Add(_delay)) && dict["RetryAttempt"] == "1" && dict.ContainsKey("Error")))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.Add(_delay)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DoesNotFailOnInvalidCronExpression_AndSimplySetsNextExecutionToNull_WhenRetryAttemptsExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["Cron"] = "some garbage"; _recurringJob["RetryAttempt"] = "10"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_ClearsLastError_AndRetryAttempts_AfterSuccessfulScheduling(bool batching) { // Arrange SetupConnection(batching); var scheduler = CreateScheduler(); _recurringJob["Error"] = "Some error that previously happened"; _recurringJob["RetryAttempt"] = "10"; // Act scheduler.Execute(_context.Object); // Assert _factory.Verify(x => x.Create(It.IsNotNull())); _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict["Error"] == String.Empty && dict["RetryAttempt"] == "0"))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_ReschedulesRecurringJob_WhenThereAreIssuesWithJobLoading_AndRetryAttemptsAreNotExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["Job"] = null; _recurringJob["V"] = "2"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert Assert.True(_delay > TimeSpan.Zero); _factory.Verify(x => x.Create(It.IsAny()), Times.Never); _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.Add(_delay)) && dict["RetryAttempt"] == "1" && dict.ContainsKey("Error")))); _transaction.Verify(x => x.AddToSet( "recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.Add(_delay)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_ReschedulesRecurringJob_WithIncreasedAttemptNumber_WhenThereAreIssuesWithJobLoading_AndRetryAttemptsAreNotExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["Job"] = null; _recurringJob["RetryAttempt"] = "1"; var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.ContainsKey("RetryAttempt") && dict["RetryAttempt"] == "2"))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_HidesRecurringJob_FromScheduler_WhenJobCanNotBeLoaded_AndRetryAttemptsExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["RetryAttempt"] = "10"; _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant); _recurringJob["Job"] = InvocationData.SerializeJob( Job.FromExpression(() => Console.WriteLine())).SerializePayload().Replace("Console", "SomeNonExistingClass"); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == String.Empty && dict["Error"].Contains("JobLoadException") && dict["V"] == "2"))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_HidesRecurringJob_FromScheduler_WhenJobCanNotBeDeserialized_AndRetryAttemptsExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["RetryAttempt"] = "10"; _recurringJob["Job"] = "Some garbage"; _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == String.Empty && dict["Error"].Contains("JsonReaderException") && dict["V"] == "2"))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_HidesRecurringJob_FromScheduler_WhenJobIsNull_AndRetryAttemptsExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["RetryAttempt"] = "10"; _recurringJob["Job"] = null; _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == String.Empty && dict["Error"].Contains("The 'Job' field has a null") && dict["V"] == "2"))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_HidesRecurringJob_FromScheduler_WhenTimeZoneCanNotBeResolved_AndRetryAttemptsExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["RetryAttempt"] = "10"; _recurringJob["TimeZoneId"] = "Non-existing time zone"; _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == String.Empty && dict["Error"].Contains("System.InvalidTimeZoneException") && dict["V"] == "2"))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_ReschedulesRecurringJob_WhenFactoryThrowsAnException_AndRetryAttemptsAreNotExceeded(bool batching) { // Arrange SetupConnection(batching); _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["V"] = "2"; _factory.Setup(x => x.Create(It.IsAny())).Throws(); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert Assert.True(_delay > TimeSpan.Zero); _factory.Verify(x => x.Create(It.IsAny()), Times.Once); _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == JobHelper.SerializeDateTime(_nowInstant.Add(_delay)) && dict["RetryAttempt"] == "1" && dict.ContainsKey("Error")))); _transaction.Verify(x => x.AddToSet( "recurring-jobs", RecurringJobId, JobHelper.ToTimestamp(_nowInstant.Add(_delay)))); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_HidesRecurringJob_FromScheduler_WhenFactoryThrowsAnException_AndRetryAttemptsExceeded(bool batching) { // Arrange SetupConnection(batching); _factory.Setup(x => x.Create(It.IsAny())).Throws(new InvalidOperationException("Invalid operation")); _recurringJob["RetryAttempt"] = "10"; _recurringJob["CreatedAt"] = JobHelper.SerializeDateTime(_nowInstant.AddDays(-1)); _recurringJob["NextExecution"] = JobHelper.SerializeDateTime(_nowInstant); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.Count == 3 && dict["NextExecution"] == String.Empty && dict["Error"].StartsWith("System.InvalidOperationException") && dict["V"] == "2"))); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1)); _transaction.Verify(x => x.Commit()); } [Theory] [InlineData(false), InlineData(true)] public void Execute_AbleToProcessFurtherJobs_WhenStateChangerThrowsAnException_ForPreviousOnes(bool batching) { // Arrange SetupConnection(batching); _schedule.Add("AnotherId"); _connection.Setup(x => x.GetAllEntriesFromHash("recurring-job:AnotherId")) .Returns(_recurringJob); _factory .Setup(x => x.Create(It.Is(ctx => (string)ctx.Parameters["RecurringJobId"] == RecurringJobId))) .Throws(); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _factory.Verify( x => x.Create(It.Is(ctx => (string)ctx.Parameters["RecurringJobId"] == "AnotherId")), Times.Once); } [Theory] [InlineData(false), InlineData(true)] public void Execute_RemovesNonExistingRecurringJobFromSet_AndDoesNotStopPipelineImmediatelyInThisCase(bool batching) { // Arrange SetupConnection(batching); _schedule.Add("AnotherId"); _connection.Setup(x => x.GetAllEntriesFromHash("recurring-job:AnotherId")).Returns(_recurringJob); _connection.Setup(x => x.GetAllEntriesFromHash($"recurring-job:{RecurringJobId}")).Returns>(null); var scheduler = CreateScheduler(); // Act scheduler.Execute(_context.Object); // Assert _transaction.Verify(x => x.RemoveFromSet("recurring-jobs", RecurringJobId)); _factory.Verify( x => x.Create(It.Is(ctx => (string)ctx.Parameters["RecurringJobId"] == "AnotherId")), Times.Once); } [Theory] [InlineData(false), InlineData(true)] public void Execute_DoesNotRescheduleRecurringJob_WhenExceptionRaisedFromTransactionCommit(bool batching) { // Arrange SetupConnection(batching); _recurringJob["RetryAttempt"] = "10"; _transaction.SetupSequence(x => x.Commit()) .Throws() .Pass(); var scheduler = CreateScheduler(); // Act Assert.Throws(() => scheduler.Execute(_context.Object)); // Assert _transaction.Verify( x => x.SetRangeInHash(It.IsAny(), It.Is>(dict => dict.ContainsKey("Error") && dict["NextExecution"] == String.Empty)), Times.Never); _transaction.Verify(x => x.AddToSet("recurring-jobs", RecurringJobId, -1), Times.Never); _transaction.Verify(x => x.Commit(), Times.Once); } private void SetupConnection(bool useJobStorageConnection) { if (useJobStorageConnection) EnableBatching(); } private void EnableBatching() { _connection .Setup(x => x.GetFirstByLowestScoreFromSet(null, It.IsAny(), It.IsAny(), It.IsAny())) .Throws(new ArgumentNullException("key")); } private RecurringJobScheduler CreateScheduler(DateTime? lastExecution = null, TimeSpan? delay = null) { var scheduler = new RecurringJobScheduler( _factory.Object, delay ?? _delay, _timeZoneResolver.Object, _nowInstantFactory); if (lastExecution.HasValue) { _recurringJob.Add("LastExecution", JobHelper.SerializeDateTime(lastExecution.Value)); } return scheduler; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/ServerJobCancellationTokenFacts.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using Hangfire.Server; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Server { public class ServerJobCancellationTokenFacts : IDisposable { private const string ServerId = "some-server"; private const string WorkerId = "1"; private const string JobId = "my-job"; private readonly Mock _connection; private readonly StateData _stateData; private readonly CancellationTokenSource _shutdownCts; private readonly CancellationTokenSource _cts; public ServerJobCancellationTokenFacts() { _stateData = new StateData { Name = ProcessingState.StateName, Data = new Dictionary { { "ServerId", ServerId }, { "WorkerId", WorkerId }, } }; _connection = new Mock(); _connection.Setup(x => x.GetStateData(JobId)).Returns(_stateData); _cts = new CancellationTokenSource(); _shutdownCts = new CancellationTokenSource(); ServerJobCancellationToken.AddServer(ServerId); } public void Dispose() { ServerJobCancellationToken.RemoveServer(ServerId); } [Fact] public void Ctor_ThrowsAnException_WhenConnectionIsNull() { var exception = Assert.Throws( () => new ServerJobCancellationToken( null, JobId, ServerId, WorkerId, _shutdownCts.Token)); Assert.Equal("connection", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenJobIdIsNull() { var exception = Assert.Throws( () => new ServerJobCancellationToken( _connection.Object, null, ServerId, WorkerId, _shutdownCts.Token)); Assert.Equal("jobId", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenServerIdIsIsNull() { var exception = Assert.Throws( () => new ServerJobCancellationToken( _connection.Object, JobId, null, WorkerId, _shutdownCts.Token)); Assert.Equal("serverId", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenWorkerIdIsNull() { var exception = Assert.Throws( () => new ServerJobCancellationToken( _connection.Object, JobId, ServerId, null, _shutdownCts.Token)); Assert.Equal("workerId", exception.ParamName); } [Fact] public void ShutdownTokenProperty_PointsToValue_LinkedWithShutdownToken() { var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); _shutdownCts.Cancel(); Assert.True(token.ShutdownToken.IsCancellationRequested); } [Fact] public void ThrowIfCancellationRequested_DoesNotThrowOnProcessingJob_IfNoShutdownRequested() { var token = CreateToken(); // Does not throw token.ThrowIfCancellationRequested(); } [Fact] public void ThrowIfCancellationRequested_ThrowsOperationCanceled_OnShutdownRequest() { _shutdownCts.Cancel(); var token = CreateToken(); Assert.Throws( () => token.ThrowIfCancellationRequested()); } [Fact] public void ThrowIfCancellationRequested_Throws_IfStateDataDoesNotExist() { _connection.Setup(x => x.GetStateData(It.IsAny())).Returns((StateData)null); var token = CreateToken(); Assert.Throws(() => token.ThrowIfCancellationRequested()); } [Fact] public void ThrowIfCancellationRequested_ThrowsJobAborted_IfJobIsNotInProcessingState() { _stateData.Name = "NotProcessing"; var token = CreateToken(); Assert.Throws( () => token.ThrowIfCancellationRequested()); } [Fact] public void ThrowIfCancellationRequested_ThrowsJobAborted_IfServerIdWasChanged() { _stateData.Data["ServerId"] = "another-server"; var token = CreateToken(); Assert.Throws( () => token.ThrowIfCancellationRequested()); } [Fact] public void ThrowIfCancellationRequested_ThrowsJobAborted_IfWorkerIdWasChanged() { _stateData.Data["WorkerId"] = "999"; var token = CreateToken(); Assert.Throws( () => token.ThrowIfCancellationRequested()); } [Fact] public void CancellationToken_IsInitializedAsNotCancelled() { var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); } [Fact] public void CheckAllCancellationTokens_DoesNotAbortCancellationToken_IfNothingChanged() { var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.False(token.IsAborted); token.ShutdownToken.ThrowIfCancellationRequested(); // does not throw } [Fact] public void CheckAllCancellationTokens_AbortsCancellationToken_IfStateDataDoesNotExist() { _connection.Setup(x => x.GetStateData(It.IsAny())).Returns((StateData)null); var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.Throws( () => token.ShutdownToken.ThrowIfCancellationRequested()); Assert.True(token.IsAborted); } [Fact] public void CheckAllCancellationTokens_AbortsCancellationToken_IfJobIsNotInProcessingState() { _stateData.Name = "NotProcessing"; var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.Throws( () => token.ShutdownToken.ThrowIfCancellationRequested()); Assert.True(token.IsAborted); } [Fact] public void CheckAllCancellationTokens_AbortsCancellationToken_IfServerIdWasChanged() { _stateData.Data["ServerId"] = "another-server"; var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.Throws( () => token.ShutdownToken.ThrowIfCancellationRequested()); Assert.True(token.IsAborted); } [Fact] public void CheckAllCancellationTokens_AbortsCancellationToken_IfWorkerIdWasChanged() { _stateData.Data["WorkerId"] = "999"; var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.Throws( () => token.ShutdownToken.ThrowIfCancellationRequested()); Assert.True(token.IsAborted); } [Fact] public void CheckAllCancellationTokens_DoesNotAbortJobsFromOtherServers() { _stateData.Name = "NotProcessing"; var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens("another-id", _connection.Object, _cts.Token); token.ShutdownToken.ThrowIfCancellationRequested(); Assert.False(token.IsAborted); } [Fact] public void CheckAllCancellationTokens_DoesNotPerformChecks_WhenShutdownTokenWasNotInitialized() { _stateData.Name = "NotProcessing"; var token = CreateToken(); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.False(token.IsAborted); _connection.Verify(x => x.GetStateData(It.IsAny()), Times.Never); } [Fact] public void CheckAllCancellationTokens_DoesNotPerformChecks_WhenJobIsAlreadyAborted() { _stateData.Name = "NotProcessing"; var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.True(token.IsAborted); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.True(token.IsAborted); _connection.Verify(x => x.GetStateData(It.IsAny()), Times.Once); } [Fact] public void CheckAllCancellationTokens_PerformsAdditionalChecks_WhenPriorOnesDidNotLeadToAbort() { var token = CreateToken(); Assert.False(token.ShutdownToken.IsCancellationRequested); ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.False(token.IsAborted); _stateData.Name = "NotProcessing"; ServerJobCancellationToken.CheckAllCancellationTokens(ServerId, _connection.Object, _cts.Token); Assert.True(token.IsAborted); _connection.Verify(x => x.GetStateData(It.IsAny()), Times.Exactly(2)); } private ServerJobCancellationToken CreateToken(string serverId = null) { return new ServerJobCancellationToken(_connection.Object, JobId, serverId ?? ServerId, WorkerId, _shutdownCts.Token); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/ServerJobCancellationWatcherFacts.cs ================================================ using System; using System.Collections.Concurrent; using System.Threading; using Hangfire.Server; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.Server { public class ServerJobCancellationWatcherFacts : IDisposable { private readonly Mock _connection; private readonly BackgroundProcessContextMock _context; private readonly TimeSpan _checkInterval; public ServerJobCancellationWatcherFacts() { _checkInterval = Timeout.InfiniteTimeSpan; _context = new BackgroundProcessContextMock(); _context.StoppingTokenSource.Cancel(); _connection = new Mock(); _context.Storage.Setup(x => x.GetConnection()).Returns(_connection.Object); ServerJobCancellationToken.AddServer(_context.ServerId); } public void Dispose() { ServerJobCancellationToken.RemoveServer(_context.ServerId); } [Fact] public void Execute_DelegatesCancellationToServerJobCancellationToken() { var token = new ServerJobCancellationToken(_connection.Object, "job-id", _context.ServerId, "1", _context.StoppedTokenSource.Token); Assert.False(token.ShutdownToken.IsCancellationRequested); _connection.Setup(x => x.GetStateData(It.IsAny())).Returns((StateData)null); var watchdog = new ServerJobCancellationWatcher(_checkInterval); Assert.Throws(() => watchdog.Execute(_context.Object)); _connection.Verify(x => x.GetStateData("job-id")); _connection.Verify(x => x.Dispose(), Times.Once); Assert.True(token.IsAborted); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/ServerWatchdogFacts.cs ================================================ using System; using System.Threading; using Hangfire.Server; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.Server { public class ServerWatchdogFacts { private readonly Mock _connection; private readonly BackgroundProcessContextMock _context; private readonly TimeSpan _checkInterval; private readonly TimeSpan _serverTimeout; public ServerWatchdogFacts() { _checkInterval = Timeout.InfiniteTimeSpan; _serverTimeout = TimeSpan.FromSeconds(5); _context = new BackgroundProcessContextMock(); _context.StoppingTokenSource.Cancel(); _connection = new Mock(); _context.Storage.Setup(x => x.GetConnection()).Returns(_connection.Object); } [Fact] public void Execute_DelegatesRemovalToStorageConnection() { _connection.Setup(x => x.RemoveTimedOutServers(It.IsAny())).Returns(1); var watchdog = new ServerWatchdog(_checkInterval, _serverTimeout); Assert.Throws(() => watchdog.Execute(_context.Object)); _connection.Verify(x => x.RemoveTimedOutServers(_serverTimeout)); _connection.Verify(x => x.Dispose(), Times.Once); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Server/WorkerFacts.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Hangfire.Common; using Hangfire.Server; using Hangfire.States; using Hangfire.Storage; using Moq; using Moq.Sequences; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.Server { public class WorkerFacts { private const string JobId = "my-job"; private readonly string[] _queues; private readonly Mock _connection; private readonly Mock _stateChanger; private readonly Mock _fetchedJob; private readonly Mock _performer; private readonly BackgroundProcessContextMock _context; public WorkerFacts() { _context = new BackgroundProcessContextMock(); _queues = new[] {"critical"}; _performer = new Mock(); _connection = new Mock(); _context.Storage.Setup(x => x.GetConnection()).Returns(_connection.Object); _fetchedJob = new Mock(); _fetchedJob.Setup(x => x.JobId).Returns(JobId); _connection .Setup(x => x.FetchNextJob(_queues, It.IsNotNull())) .Returns(_fetchedJob.Object); _connection.Setup(x => x.GetJobData(JobId)) .Returns(new JobData { Job = Job.FromExpression(() => Method()), }); _stateChanger = new Mock(); _stateChanger.Setup(x => x.ChangeState(It.IsAny())) .Returns(ctx => ctx.NewState); } [Fact] public void Ctor_ThrowsAnException_WhenQueuesCollectionNull() { var exception = Assert.Throws( () => new Worker(null, _performer.Object, _stateChanger.Object)); Assert.Equal("queues", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenPerformanceProcessIsNull() { var exception = Assert.Throws( () => new Worker(_queues, null, _stateChanger.Object)); Assert.Equal("performer", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenStateChangeProcess_IsNull() { var exception = Assert.Throws( () => new Worker(_queues, _performer.Object, null)); Assert.Equal("stateChanger", exception.ParamName); } [Fact] public void Execute_TakesConnectionAndReleasesIt() { var worker = CreateWorker(); worker.Execute(_context.Object); _context.Storage.Verify(x => x.GetConnection(), Times.Once); _connection.Verify(x => x.Dispose(), Times.Once); } [Fact] public void Execute_FetchesAJobAndRemovesItFromQueue() { var worker = CreateWorker(); worker.Execute(_context.Object); _connection.Verify( x => x.FetchNextJob(_queues, _context.StoppingTokenSource.Token), Times.Once); _fetchedJob.Verify(x => x.RemoveFromQueue()); } [Fact] public void Execute_RequeuesAJob_WhenThereWasAnException() { _stateChanger .Setup(x => x.ChangeState(It.IsAny())) .Throws(); var worker = CreateWorker(1); Assert.Throws( () => worker.Execute(_context.Object)); _fetchedJob.Verify(x => x.RemoveFromQueue(), Times.Never); _fetchedJob.Verify(x => x.Requeue()); } [Fact] public void Execute_MovesAJobToTheFailedState_WithFiltersDisabled_WhenStateChangerThrowsAnException() { _stateChanger .Setup(x => x.ChangeState(It.Is(y => y.NewState.Name != FailedState.StateName))) .Throws(); var worker = CreateWorker(1); worker.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(y => y.NewState.Name == FailedState.StateName && y.DisableFilters == true))); _fetchedJob.Verify(x => x.RemoveFromQueue(), Times.Once); _fetchedJob.Verify(x => x.Requeue(), Times.Never); } [Fact, Sequence] public void Execute_ExecutesDefaultWorkflow_WhenJobIsCorrect() { // Arrange _stateChanger .Setup(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == JobId && ctx.NewState is ProcessingState))) .InSequence() .Returns(ctx => ctx.NewState); _performer.Setup(x => x.Perform(It.IsAny())) .InSequence(); _stateChanger .Setup(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == JobId && ctx.NewState is SucceededState))) .InSequence() .Returns(context => context.NewState); var worker = CreateWorker(); // Act worker.Execute(_context.Object); // Assert - see the `SequenceAttribute` class. } [Fact] public void Execute_SetsCurrentServer_ToProcessingState() { var worker = CreateWorker(); worker.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.NewState is ProcessingState && (((ProcessingState) ctx.NewState).ServerId == _context.ServerId)))); } [Fact] public void Execute_ProcessesOnlyJobs_InEnqueued_Scheduled_AndProcessingStates() { var worker = CreateWorker(); worker.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.NewState is ProcessingState && ctx.ExpectedStates.ElementAt(0) == EnqueuedState.StateName && ctx.ExpectedStates.ElementAt(1) == ScheduledState.StateName && ctx.ExpectedStates.ElementAt(2) == ProcessingState.StateName && ctx.ExpectedStates.Count() == 3))); } [Fact] public void Execute_DoesNotDisableFilters_DuringNormalOperation() { var worker = CreateWorker(); worker.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.DisableFilters == false))); } [Fact] public void Execute_DoesNotRun_PerformanceProcess_IfTransitionToProcessingStateFailed() { // Arrange _stateChanger .Setup(x => x.ChangeState(It.Is(ctx => ctx.NewState is ProcessingState))) .Returns(null); var worker = CreateWorker(); // Act worker.Execute(_context.Object); // Assert _performer.Verify(x => x.Perform(It.IsAny()), Times.Never); } [Fact] public void Execute_Runs_PerformanceProcess() { var worker = CreateWorker(); worker.Execute(_context.Object); _performer.Verify(x => x.Perform(It.Is(ctx => ctx.BackgroundJob.Id == JobId && ctx.ServerId == _context.ServerId))); } [Fact] public void Execute_DoesNotMoveAJob_ToTheFailedState_ButRequeuesIt_WhenProcessThrowsOperationCanceled_DuringShutdownOnly() { // Arrange var cts = new CancellationTokenSource(); _context.StoppedTokenSource = cts; _performer.Setup(x => x.Perform(It.IsAny())) .Callback(() => cts.Cancel()) .Throws(); var worker = CreateWorker(); // Act Assert.Throws(() => worker.Execute(_context.Object)); // Assert _stateChanger.Verify( x => x.ChangeState(It.Is(ctx => ctx.NewState is FailedState)), Times.Never); _fetchedJob.Verify(x => x.Requeue()); } [Fact] public void Execute_MovesAJob_ToTheFailedState_AndNotRequeuesIt_WhenProcessThrowsOperationCanceled_WhenShutdownWasNotRequested() { // Arrange _performer.Setup(x => x.Perform(It.IsAny())) .Throws(); var worker = CreateWorker(); // Act worker.Execute(_context.Object); // Assert _stateChanger.Verify( x => x.ChangeState(It.Is(ctx => ctx.NewState is FailedState)), Times.Once); _fetchedJob.Verify(x => x.Requeue(), Times.Never); } [Fact] public void Execute_DoesNotMoveAJobToFailedState_AndRemovesJobFromQueue_WhenProcessThrowsJobAbortedException() { // Arrange _performer.Setup(x => x.Perform(It.IsAny())) .Throws(); var worker = CreateWorker(); // Act worker.Execute(_context.Object); _stateChanger.Verify( x => x.ChangeState(It.Is(ctx => ctx.NewState is FailedState)), Times.Never); _fetchedJob.Verify(x => x.RemoveFromQueue()); _fetchedJob.Verify(x => x.Requeue(), Times.Never); } [Fact] public void Execute_MovesJob_ToSuccessfulState_OnlyIfItIsInProcessingState() { var worker = CreateWorker(); worker.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.NewState is SucceededState && ctx.ExpectedStates.ElementAt(0) == ProcessingState.StateName))); } [Fact] public void Execute_PassesCustomData_BetweenContexts_OnSucceededStateTransition() { var worker = CreateWorker(); _performer.Setup(x => x.Perform(It.IsNotNull())) .Callback(ctx => ctx.Items.Add("Key", "Value")); worker.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.NewState is SucceededState && ctx.CustomData["Key"].Equals("Value")))); } [Fact] public void Execute_MovesJob_ToFailedState_IfThereWasInternalException() { // Arrange var exception = new InvalidOperationException(); _performer .Setup(x => x.Perform(It.IsAny())) .Throws(exception); var worker = CreateWorker(); // Act worker.Execute(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == JobId && ctx.NewState is FailedState && ((FailedState) ctx.NewState).Exception == exception && ctx.DisableFilters == false))); } [Fact] public void Execute_DoesNotPassCustomData_BetweenContexts_OnFailedStateTransition() { var worker = CreateWorker(); _performer.Setup(x => x.Perform(It.IsNotNull())) .Callback(ctx => ctx.Items.Add("Key", "Value")) .Throws(); worker.Execute(_context.Object); _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.NewState is FailedState && ctx.CustomData == null))); } [Fact] public void Execute_MovesJob_ToFailedState_IfThereWasUserException() { // Arrange var exception = new InvalidOperationException(); _performer .Setup(x => x.Perform(It.IsAny())) .Throws(new JobPerformanceException("hello", exception)); var worker = CreateWorker(); // Act worker.Execute(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.BackgroundJobId == JobId && ctx.NewState is FailedState && ctx.DisableFilters == false))); } [Fact] public void Execute_MovesJob_ToFailedState_IfThereWasJobLoadException() { // Arrange _connection.Setup(x => x.GetJobData(JobId)) .Returns(new JobData { LoadException = new JobLoadException("asd", new Exception()) }); var worker = CreateWorker(); // Act worker.Execute(_context.Object); // Assert _stateChanger.Verify(x => x.ChangeState(It.Is(ctx => ctx.NewState is FailedState && ctx.DisableFilters == false))); } private Worker CreateWorker(int maxStateChangeAttempts = 10) { return new Worker(_queues, _performer.Object, _stateChanger.Object, TimeSpan.FromSeconds(5), maxStateChangeAttempts); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Method() { } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/ApplyStateContextFacts.cs ================================================ using System; using System.Collections.Generic; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.States { public class ApplyStateContextFacts { private const string OldState = "SomeState"; private const string NewState = "NewState"; private readonly Mock _newState; private readonly Mock _storage; private readonly BackgroundJobMock _backgroundJob; private readonly Mock _transaction; private readonly Mock _connection; private readonly Mock _profiler; private readonly Mock _stateMachine; private readonly Mock> _customData; public ApplyStateContextFacts() { _storage = new Mock(); _connection = new Mock(); _transaction = new Mock(); _backgroundJob = new BackgroundJobMock(); _newState = new Mock(); _newState.Setup(x => x.Name).Returns(NewState); _profiler = new Mock(); _stateMachine = new Mock(); _customData = new Mock>(); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new ApplyStateContext(null, _connection.Object, _transaction.Object, _backgroundJob.Object, _newState.Object, OldState)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenConnectionIsNull() { var exception = Assert.Throws( () => new ApplyStateContext(_storage.Object, null, _transaction.Object, _backgroundJob.Object, _newState.Object, OldState)); Assert.Equal("connection", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenTransactionIsNull() { var exception = Assert.Throws( () => new ApplyStateContext(_storage.Object, _connection.Object, null, _backgroundJob.Object, _newState.Object, OldState)); Assert.Equal("transaction", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenBackgroundJobIsNull() { var exception = Assert.Throws( () => new ApplyStateContext(_storage.Object, _connection.Object, _transaction.Object, null, _newState.Object, OldState)); Assert.Equal("backgroundJob", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenNewStateIsNull() { var exception = Assert.Throws( () => new ApplyStateContext(_storage.Object, _connection.Object, _transaction.Object, _backgroundJob.Object, null, OldState)); Assert.Equal("newState", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenProfilerIsNull() { var exception = Assert.Throws( () => new ApplyStateContext( _storage.Object, _connection.Object, _transaction.Object, _backgroundJob.Object, _newState.Object, OldState, null, _stateMachine.Object)); Assert.Equal("profiler", exception.ParamName); } [Fact] public void Ctor_ShouldSetPropertiesCorrectly() { var context = new ApplyStateContext( _storage.Object, _connection.Object, _transaction.Object, _backgroundJob.Object, _newState.Object, OldState); Assert.Same(_storage.Object, context.Storage); Assert.Same(_connection.Object, context.Connection); Assert.Same(_transaction.Object, context.Transaction); Assert.Same(_backgroundJob.Object, context.BackgroundJob); Assert.Equal(OldState, context.OldStateName); Assert.Same(_newState.Object, context.NewState); Assert.Equal(_storage.Object.JobExpirationTimeout, context.JobExpirationTimeout); } [Fact] public void InternalCtor_CorrectlySetsAllTheProperties() { var context = new ApplyStateContext( _storage.Object, _connection.Object, _transaction.Object, _backgroundJob.Object, _newState.Object, OldState, _profiler.Object, _stateMachine.Object, _customData.Object); Assert.Same(_storage.Object, context.Storage); Assert.Same(_connection.Object, context.Connection); Assert.Same(_transaction.Object, context.Transaction); Assert.Same(_backgroundJob.Object, context.BackgroundJob); Assert.Equal(OldState, context.OldStateName); Assert.Same(_newState.Object, context.NewState); Assert.Equal(_storage.Object.JobExpirationTimeout, context.JobExpirationTimeout); Assert.Same(_stateMachine.Object, context.StateMachine); Assert.Same(_customData.Object, context.CustomData); } [Fact] public void CopyCtor_ForElectStateContext_CorrectlySetsAllTheProperties() { var electContext = new ElectStateContextMock(); var context = new ApplyStateContext(_transaction.Object, electContext.Object); Assert.Same(electContext.Object.Storage, context.Storage); Assert.Same(electContext.Object.Connection, context.Connection); Assert.Same(_transaction.Object, context.Transaction); Assert.Same(electContext.Object.BackgroundJob, context.BackgroundJob); Assert.Equal(electContext.Object.CurrentState, context.OldStateName); Assert.Same(electContext.Object.CandidateState, context.NewState); Assert.Null(context.CustomData); } [Fact] public void CopyCtor_ForElectStateContext_CopiesCustomData_ToAnotherDictionary() { // Arrange var dictionary = new Dictionary { { "lalala", new object() } }; var electContext = new ElectStateContextMock { ApplyContext = { CustomData = dictionary } }; // Act var context = new ApplyStateContext(_transaction.Object, electContext.Object); // Assert Assert.Equal(electContext.Object.CustomData, context.CustomData); Assert.NotSame(electContext.Object.CustomData, context.CustomData); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/AwaitingStateFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.States { public class AwaitingStateFacts { [Fact] public void StateName_IsEqualToAwaiting() { Assert.Equal("Awaiting", AwaitingState.StateName); } [Fact] public void NameProperty_ReturnsStateName() { var state = CreateState(); Assert.Equal(AwaitingState.StateName, state.Name); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void SerializeData_ReturnsCorrectData_Before170() { var state = CreateState(); var data = state.SerializeData(); Assert.Equal(state.ParentId, data["ParentId"]); Assert.Equal("{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\",\"Reason\":null}", data["NextState"]); Assert.Equal(state.Options.ToString("G"), data["Options"]); Assert.Equal(state.Expiration.ToString(), data["Expiration"]); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializeData_ReturnsCorrectData_After170() { var state = CreateState(); var data = state.SerializeData(); Assert.Equal(state.ParentId, data["ParentId"]); Assert.Equal("{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\"}", data["NextState"]); Assert.Equal(state.Options.ToString("D"), data["Options"]); Assert.False(data.ContainsKey("Expiration")); } [Fact] public void IsFinal_ReturnsFalse() { var state = CreateState(); Assert.False(state.IsFinal); } [Fact] public void IgnoreExceptions_ReturnsFalse() { var state = CreateState(); Assert.False(state.IgnoreJobLoadException); } [Fact, CleanSerializerSettings] public void SerializeData_HandlesChangingProcessOfInternalDataSerialization() { SerializationHelper.SetUserSerializerSettings(SerializerSettingsHelper.DangerousSettings); var nextStateSerialized = SerializationHelper.Serialize(new EnqueuedState(), SerializationOption.User); var nextState = SerializationHelper.Deserialize(nextStateSerialized, SerializationOption.TypedInternal) as EnqueuedState; Assert.NotNull(nextState); Assert.NotEqual(default(DateTime), nextState.EnqueuedAt); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_Before170() { var state = new AwaitingState("parent"); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.AwaitingState, Hangfire.Core\",\"ParentId\":\"parent\",\"NextState\":{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\",\"Reason\":null},\"Options\":0,\"Reason\":null}", serialized); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_After170() { var state = new AwaitingState("parent"); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.AwaitingState, Hangfire.Core\",\"ParentId\":\"parent\",\"NextState\":{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\"}}", serialized); } [DataCompatibilityRangeFact] public void JsonDeserialize_CanHandlePreviousFormat() { var json = "{\"$type\":\"Hangfire.States.AwaitingState, Hangfire.Core\",\"ParentId\":\"parent\",\"NextState\":{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\"},\"Options\":1,\"Name\":\"Awaiting\"}"; var state = SerializationHelper.Deserialize(json, SerializationOption.TypedInternal); Assert.Equal("parent", state.ParentId); Assert.Equal("Enqueued", state.NextState.Name); } [DataCompatibilityRangeFact] public void JsonDeserialize_CanHandleNewFormat() { var json = "{\"$type\":\"Hangfire.States.AwaitingState, Hangfire.Core\",\"ParentId\":\"parent\",\"NextState\":{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\"}}"; var state = SerializationHelper.Deserialize(json, SerializationOption.TypedInternal); Assert.Equal("parent", state.ParentId); Assert.Null(state.Reason); Assert.Equal("Enqueued", state.NextState.Name); } private static AwaitingState CreateState() { return new AwaitingState("1", new EnqueuedState(), JobContinuationOptions.OnlyOnSucceededState, TimeSpan.FromDays(1)); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/AwaitingStateHandlerFacts.cs ================================================ using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.States { public class AwaitingStateHandlerFacts { private readonly ApplyStateContextMock _context; private readonly Mock _transactionMock = new Mock(); public AwaitingStateHandlerFacts() { _context = new ApplyStateContextMock(); } [Fact] public void ShouldWorkOnlyWithAwaitingState() { var handler = new AwaitingState.Handler(); Assert.Equal(AwaitingState.StateName, handler.StateName); } [Fact] public void Apply_ShouldAddToSet_Awaiting() { var handler = new AwaitingState.Handler(); handler.Apply(_context.Object, _transactionMock.Object); _transactionMock.Verify(x => x.AddToSet("awaiting", "JobId", It.IsAny()), Times.Once); } [Fact] public void Unapply_ShouldRemoveFromSet_Awaiting() { var handler = new AwaitingState.Handler(); handler.Unapply(_context.Object, _transactionMock.Object); _transactionMock.Verify(x => x.RemoveFromSet("awaiting", "JobId"), Times.Once); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/BackgroundJobStateChangerFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.States { public class BackgroundJobStateChangerFacts { private const string StateName = "State"; private const string JobId = "1"; private const string OldStateName = "Old"; private static readonly string[] FromOldState = { OldStateName }; private readonly Mock _connection; private readonly Job _job; private readonly Mock _state; private readonly Mock _filterProvider; private readonly Mock _stateMachine; private readonly Mock _distributedLock; private readonly Mock _transaction; private readonly CancellationTokenSource _cts; private readonly StateChangeContextMock _context; public BackgroundJobStateChangerFacts() { _stateMachine = new Mock(); _filterProvider = new Mock(); _filterProvider.Setup(x => x.GetFilters(It.IsAny())).Returns(Enumerable.Empty()); _job = Job.FromExpression(() => Console.WriteLine()); _state = new Mock(); _state.Setup(x => x.Name).Returns(StateName); _connection = new Mock(); _transaction = new Mock(); _connection.Setup(x => x.CreateWriteTransaction()).Returns(_transaction.Object); _connection.Setup(x => x.CreateExpiredJob( It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns(JobId); _connection.Setup(x => x.GetJobData(JobId)) .Returns(new JobData { State = OldStateName, Job = _job }); _distributedLock = new Mock(); _connection .Setup(x => x.AcquireDistributedLock($"job:{JobId}:state-lock", It.IsAny())) .Returns(_distributedLock.Object); _cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); _context = new StateChangeContextMock { BackgroundJobId = JobId, Connection = _connection, CancellationToken = _cts.Token, NewState = _state, ExpectedStates = FromOldState, CustomData = new Dictionary { { "Key", "Value" } } }; _stateMachine.Setup(x => x.ApplyState(It.IsNotNull())) .Returns(_context.NewState.Object); } [Fact] public void Ctor_ThrowsAnException_WhenFilterProviderIsNull() { var exception = Assert.Throws( () => new BackgroundJobStateChanger((IJobFilterProvider)null)); Assert.Equal("filterProvider", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenStateMachineNull() { var exception = Assert.Throws( () => new BackgroundJobStateChanger(_filterProvider.Object, null)); Assert.Equal("stateMachine", exception.ParamName); } [Fact] public void ChangeState_WorksWithinAJobLock() { var stateChanger = CreateStateChanger(); stateChanger.ChangeState(_context.Object); _distributedLock.Verify(x => x.Dispose()); } [Fact] public void TryToChangeState_ChangesTheStateOfTheJob() { // Arrange var stateChanger = CreateStateChanger(); // Act var result = stateChanger.ChangeState(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState( It.Is(sc => sc.BackgroundJob.Id == JobId && sc.BackgroundJob.Job.Type.Name.Equals("Console") && sc.NewState == _state.Object && sc.OldStateName == OldStateName))); Assert.NotNull(result); Assert.Equal(_state.Object.Name, result.Name); } [Fact] public void ChangeState_PassesCustomData_ToApplyStateContext() { var stateChanger = CreateStateChanger(); stateChanger.ChangeState(_context.Object); _stateMachine.Verify(x => x.ApplyState(It.Is( sc => sc.CustomData["Key"].Equals("Value")))); } [Fact] public void ChangeState_ChangesTheStateOfTheJob_WhenFromStatesIsNull() { // Arrange var stateChanger = CreateStateChanger(); _context.ExpectedStates = null; // Act stateChanger.ChangeState(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState( It.Is(ctx => ctx.NewState == _state.Object && ctx.OldStateName == OldStateName))); } [Fact] public void ChangeState_ReturnsNull_WhenJobIsNotFound() { // Arrange _connection.Setup(x => x.GetJobData(It.IsAny())) .Returns((JobData)null); var stateChanger = CreateStateChanger(); // Act var result = stateChanger.ChangeState(_context.Object); // Assert Assert.Null(result); _connection.Verify(x => x.GetJobData(JobId)); _stateMachine.Verify( x => x.ApplyState(It.IsAny()), Times.Never); } [Fact] public void ChangeState_DoesNotDoAnything_WhenStateIsNull_AndCancellationTokenIsCancelled() { // Arrange _connection.Setup(x => x.GetJobData(It.IsAny())).Returns(new JobData { Job = _job, State = null }); var stateChanger = CreateStateChanger(); _cts.Cancel(); // Act var result = stateChanger.ChangeState(_context.Object); // Assert Assert.Null(result); _stateMachine.Verify( x => x.ApplyState(It.IsAny()), Times.Never); } [Fact] public void ChangeState_WaitsFor_NonNullJobDataAndStateValue() { // Arrange var results = new Queue(); results.Enqueue(null); results.Enqueue(new JobData { Job = _job, State = null }); results.Enqueue(new JobData { Job = _job, State = OldStateName }); _connection.Setup(x => x.GetJobData(It.IsAny())) .Returns(results.Dequeue); var stateChanger = CreateStateChanger(); // Act var result = stateChanger.ChangeState(_context.Object); // Assert Assert.Empty(results); Assert.NotNull(result); Assert.Equal(_state.Object.Name, result.Name); } [Fact] public void ChangeState_ReturnsNull_WhenFromStatesArgumentDoesNotContainCurrentState() { // Arrange var stateChanger = CreateStateChanger(); _context.ExpectedStates = new[] { "AnotherState" }; // Act var result = stateChanger.ChangeState(_context.Object); // Assert Assert.Null(result); _stateMachine.Verify( x => x.ApplyState(It.IsAny()), Times.Never); } [Fact] public void ChangeState_ThrowsAnException_WhenApplyStateThrowsException() { // Arrange _stateMachine.Setup(x => x.ApplyState(It.IsAny())) .Throws(new FieldAccessException()); var stateChanger = CreateStateChanger(); // Act & Assert Assert.Throws( () => stateChanger.ChangeState(_context.Object)); } [Fact] public void ChangeState_MoveJobToTheFailedState_IfMethodDataCouldNotBeResolved() { // Arrange _connection.Setup(x => x.GetJobData(JobId)) .Returns(new JobData { State = OldStateName, Job = null, LoadException = new JobLoadException("asd", new InvalidOperationException()) }); var stateChanger = CreateStateChanger(); // Act stateChanger.ChangeState(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState( It.Is(ctx => ctx.BackgroundJob.Id == JobId && ctx.BackgroundJob.Job == null && ctx.NewState is FailedState))); } [Fact] public void ChangeState_MoveJobToTheGivenState_IfStateIgnoresThisException_AndMethodDataCouldNotBeResolved() { // Arrange _connection.Setup(x => x.GetJobData(JobId)) .Returns(new JobData { State = OldStateName, Job = null, LoadException = new JobLoadException("asd", new Exception()) }); _state.Setup(x => x.IgnoreJobLoadException).Returns(true); var stateChanger = CreateStateChanger(); // Act var result = stateChanger.ChangeState(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState( It.Is(ctx => ctx.NewState == _state.Object))); Assert.NotNull(result); Assert.Equal(_state.Object.Name, result.Name); } [Fact] public void ChangeState_CommitsTheNewState_AndReturnsAppliedState() { // Arrange var stateChanger = CreateStateChanger(); _context.ExpectedStates = new[] { OldStateName }; // Act var result = stateChanger.ChangeState(_context.Object); // Assert _stateMachine.Verify(x => x.ApplyState( It.Is(ctx => ctx.NewState == _state.Object && ctx.OldStateName == OldStateName && ctx.BackgroundJob.Job == _job && ctx.BackgroundJob.Id == JobId))); _transaction.Verify(x => x.Commit()); Assert.NotNull(result); Assert.Equal(_state.Object.Name, result.Name); } [Fact] public void ChangeState_ReturnsState_ReturnedByAStateMachine() { // Arrange var anotherState = new Mock(); _stateMachine.Setup(x => x.ApplyState(It.IsNotNull())) .Returns(anotherState.Object); var stateChanger = CreateStateChanger(); // Act var result = stateChanger.ChangeState(_context.Object); // Assert Assert.Same(result, anotherState.Object); } [Fact] public void ChangeState_RethrowsFilterException_AndDoesNotCommitAnything_WhenNoCancellationToken() { // Arrange _stateMachine .Setup(x => x.ApplyState(It.Is(context => context.NewState == _state.Object))) .Throws(); var stateChanger = CreateStateChanger(); // Act & Assert Assert.Throws(() => stateChanger.ChangeState(_context.Object)); _transaction.Verify(x => x.Commit(), Times.Never); _stateMachine.Verify( x => x.ApplyState(It.Is(context => context.NewState == _state.Object)), Times.Once); } [Fact] public void ChangeState_SimplyRethrowsAnException_WithoutRetriesAndFailedState() { // Arrange _stateMachine .Setup(x => x.ApplyState(It.Is(context => context.NewState == _state.Object))) .Throws(); var stateChanger = CreateStateChanger(); // Act & Assert Assert.Throws(() => stateChanger.ChangeState(_context.Object)); _connection.Verify(x => x.GetJobData(JobId), Times.Once); _connection.Verify(x => x.AcquireDistributedLock($"job:{JobId}:state-lock", It.IsAny()), Times.Once); _transaction.Verify(x => x.Commit(), Times.Never); _stateMachine.Verify( x => x.ApplyState(It.Is(context => context.NewState == _state.Object)), Times.Once); } [Fact] public void ChangeState_DoesNotApplyAnything_AndRethrowsExceptions_ThrownByElectStateFilters() { // Arrange var electStateFilter = new Mock(); electStateFilter .Setup(x => x.OnStateElection(It.IsAny())) .Throws(); _filterProvider .Setup(x => x.GetFilters(It.IsAny())) .Returns(new [] { new JobFilter(electStateFilter.Object, JobFilterScope.Method, 0) }); var stateChanger = CreateStateChanger(); // Act Assert.Throws(() => stateChanger.ChangeState(_context.Object)); // Assert electStateFilter.Verify(x => x.OnStateElection(It.IsNotNull()), Times.Once); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void ChangeState_DoesNotApplyAnything_AndRethrowsAndException_ThrownByApplyStateFilters() { // Arrange var applyStateFilter = new Mock(); applyStateFilter .Setup(x => x.OnStateApplied(It.IsAny(), It.IsAny())) .Throws(); _filterProvider .Setup(x => x.GetFilters(It.IsAny())) .Returns(new [] { new JobFilter(applyStateFilter.Object, JobFilterScope.Method, 0) }); var stateChanger = CreateStateChanger(); // Act Assert.Throws(() => stateChanger.ChangeState(_context.Object)); // Assert applyStateFilter.Verify( x => x.OnStateApplied(It.IsNotNull(), It.IsNotNull()), Times.Once); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void ChangeState_DoesNotInvokeElectStateFilters_WhenFiltersDisabled() { // Arrange var electStateFilter = new Mock(); electStateFilter .Setup(x => x.OnStateElection(It.IsAny())) .Throws(); _filterProvider .Setup(x => x.GetFilters(It.IsAny())) .Returns(new [] { new JobFilter(electStateFilter.Object, JobFilterScope.Method, 0) }); _context.DisableFilters = true; var stateChanger = CreateStateChanger(); // Act stateChanger.ChangeState(_context.Object); // Assert electStateFilter.Verify( x => x.OnStateElection(It.IsAny()), Times.Never); _stateMachine.Verify(x => x.ApplyState(It.IsNotNull())); _transaction.Verify(x => x.Commit()); } [Fact] public void ChangeState_DoesNotInvokeApplyStateFilters_WhenFiltersDisabled() { // Arrange var applyStateFilter = new Mock(); applyStateFilter .Setup(x => x.OnStateApplied(It.IsAny(), It.IsAny())) .Throws(); _filterProvider .Setup(x => x.GetFilters(It.IsAny())) .Returns(new [] { new JobFilter(applyStateFilter.Object, JobFilterScope.Method, 0) }); _context.DisableFilters = true; var stateChanger = CreateStateChanger(); // Act stateChanger.ChangeState(_context.Object); // Assert applyStateFilter.Verify( x => x.OnStateApplied(It.IsAny(), It.IsAny()), Times.Never); _stateMachine.Verify(x => x.ApplyState(It.IsNotNull())); _transaction.Verify(x => x.Commit()); } [Fact] public void ChangeState_WithTransaction_PassesTheGivenTransaction_AndDoesNotCommitTheImplicitOne() { _context.Transaction = new Mock(); var stateChanger = CreateStateChanger(); stateChanger.ChangeState(_context.Object); _stateMachine.Verify(x => x.ApplyState(It.Is( ctx => ctx.Transaction == _context.Transaction.Object))); _connection.Verify(x => x.CreateWriteTransaction(), Times.Never); _transaction.Verify(x => x.Commit(), Times.Never); } [Fact] public void ChangeState_WithTransaction_DoesNotCommitAndDoesNotDisposeTheExplicitTransaction() { _context.Transaction = new Mock(); var stateChanger = CreateStateChanger(); stateChanger.ChangeState(_context.Object); _context.Transaction.Verify(x => x.Commit(), Times.Never); _context.Transaction.Verify(x => x.Dispose(), Times.Never); } [Fact] public void ChangeState_WithTransaction_AcquiresATransactionLevelLockInstead() { _context.Transaction = new Mock(); var stateChanger = CreateStateChanger(); stateChanger.ChangeState(_context.Object); _context.Transaction.Verify(x => x.AcquireDistributedLock($"job:{JobId}:state-lock", It.IsAny())); _connection.Verify(x => x.AcquireDistributedLock(It.IsAny(), It.IsAny()), Times.Never); } private BackgroundJobStateChanger CreateStateChanger() { return new BackgroundJobStateChanger(_filterProvider.Object, _stateMachine.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/CoreStateMachineFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Hangfire.States; using Hangfire.Storage; using Moq; using Moq.Sequences; using Xunit; namespace Hangfire.Core.Tests.States { public class CoreStateMachineFacts { private const string OldStateName = "OldState"; private const string StateName = "State"; private const string JobId = "job"; private readonly Dictionary> _handlers = new Dictionary>(); private readonly Func _stateHandlersThunk; private readonly ApplyStateContextMock _applyContext; public CoreStateMachineFacts() { _stateHandlersThunk = (storage, stateName) => new CoreStateMachine.StateHandlersCollection( _handlers.TryGetValue(stateName, out var handlers) ? handlers.ToList() : new List(), Enumerable.Empty(), stateName); var backgroundJob = new BackgroundJobMock { Id = JobId }; _applyContext = new ApplyStateContextMock { BackgroundJob = backgroundJob, OldStateName = OldStateName }; _applyContext.NewState.Setup(x => x.Name).Returns(StateName); } [Fact] public void ApplyState_SetsTheNewState_ForABackgroundJob() { var stateMachine = CreateStateMachine(); stateMachine.ApplyState(_applyContext.Object); _applyContext.Transaction.Verify(x => x.SetJobState(JobId, _applyContext.NewState.Object)); } [Fact] public void ApplyState_SetsJobExpiration_IfStateIsFinal() { _applyContext.NewState.Setup(x => x.IsFinal).Returns(true); var stateMachine = CreateStateMachine(); stateMachine.ApplyState(_applyContext.Object); _applyContext.Transaction.Verify(x => x.ExpireJob(JobId, _applyContext.JobExpirationTimeout)); } [Fact] public void ApplyState_PersistTheJob_IfStateIsNotFinal() { _applyContext.NewState.Setup(x => x.IsFinal).Returns(false); var stateMachine = CreateStateMachine(); stateMachine.ApplyState(_applyContext.Object); _applyContext.Transaction.Verify(x => x.PersistJob(JobId)); } [Fact, Sequence] public void ApplyState_CallsUnapplyHandlers_BeforeSettingTheState() { // Arrange var handler1 = CreateStateHandler(OldStateName); var handler2 = CreateStateHandler(OldStateName); handler1 .Setup(x => x.Unapply(_applyContext.Object, _applyContext.Transaction.Object)) .InSequence(); handler2 .Setup(x => x.Unapply(_applyContext.Object, _applyContext.Transaction.Object)) .InSequence(); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_applyContext.Object); // Assert - Sequence } [Fact] public void ApplyState_DoesNotCallUnapplyHandlers_ForDifferentStates() { // Arrange var handler = CreateStateHandler(StateName); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_applyContext.Object); // Assert handler.Verify( x => x.Unapply(It.IsAny(), It.IsAny()), Times.Never); } [Fact, Sequence] public void ApplyState_ShouldCallApplyHandlers_AfterSettingTheState() { // Arrange var handler1 = CreateStateHandler(StateName); var handler2 = CreateStateHandler(StateName); _applyContext.Transaction .Setup(x => x.SetJobState(It.IsAny(), It.IsAny())) .InSequence(); handler1.Setup(x => x.Apply(_applyContext.Object, _applyContext.Transaction.Object)) .InSequence(); handler2.Setup(x => x.Apply(_applyContext.Object, _applyContext.Transaction.Object)) .InSequence(); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_applyContext.Object); // Assert - Sequence } [Fact] public void ApplyState_DoesNotCallApplyHandlers_ForDifferentStates() { // Arrange var handler = CreateStateHandler(OldStateName); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_applyContext.Object); // Assert handler.Verify( x => x.Apply(It.IsAny(), It.IsAny()), Times.Never); } private CoreStateMachine CreateStateMachine() { return new CoreStateMachine(_stateHandlersThunk); } private Mock CreateStateHandler(string stateName) { var handler = new Mock(); handler.Setup(x => x.StateName).Returns(stateName); if (!_handlers.ContainsKey(stateName)) _handlers.Add(stateName, new List()); _handlers[stateName].Add(handler.Object); return handler; } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/DeletedStateFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.States { public class DeletedStateFacts { [Fact] public void StateName_ReturnsDeleted() { var result = DeletedState.StateName; Assert.Equal("Deleted", result); } [Fact] public void NameProperty_ReturnsStateName() { var state = CreateState(); var result = state.Name; Assert.Equal(DeletedState.StateName, result); } [Fact] public void IsFinalProperty_ReturnsTrue() { var state = CreateState(); var result = state.IsFinal; Assert.True(result); } [Fact] public void IgnoreExceptions_ReturnsTrue() { var state = CreateState(); var result = state.IgnoreJobLoadException; Assert.True(result); } [Fact] public void DeletedAtProperty_ReturnsCurrentUtcDate() { var state = CreateState(); Assert.True(DateTime.UtcNow.AddMinutes(-1) < state.DeletedAt); Assert.True(state.DeletedAt < DateTime.UtcNow.AddMinutes(1)); } [Fact] public void SerializeData_ReturnsSerializedStateData() { var state = CreateState(); var data = state.SerializeData(); Assert.Single(data); Assert.True(JobHelper.DeserializeDateTime(data["DeletedAt"]) != default(DateTime)); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_Before170() { var state = new DeletedState(); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.DeletedState, Hangfire.Core\",\"Reason\":null}", serialized); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_After170() { var state = new DeletedState(); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.DeletedState, Hangfire.Core\"}", serialized); } private static DeletedState CreateState() { return new DeletedState(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/DeletedStateHandlerFacts.cs ================================================ using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.States { public class DeletedStateHandlerFacts { private readonly ApplyStateContextMock _context; private readonly Mock _transactionMock = new Mock(); public DeletedStateHandlerFacts() { _context = new ApplyStateContextMock(); } [Fact] public void ShouldWorkOnlyWithDeletedState() { var handler = new DeletedState.Handler(); Assert.Equal(DeletedState.StateName, handler.StateName); } [Fact] public void Apply_ShouldIncrease_DeletedCounter() { var handler = new DeletedState.Handler(); handler.Apply(_context.Object, _transactionMock.Object); _transactionMock.Verify(x => x.IncrementCounter("stats:deleted"), Times.Once); } [Fact] public void Unapply_ShouldDecrementStatistics() { var handler = new DeletedState.Handler(); handler.Unapply(_context.Object, _transactionMock.Object); _transactionMock.Verify(x => x.DecrementCounter("stats:deleted"), Times.Once); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/ElectStateContextFacts.cs ================================================ using System; using System.Collections.Generic; using Hangfire.Common; using Hangfire.States; using Moq; using Xunit; #pragma warning disable 618 // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.States { public class ElectStateContextFacts { private readonly ApplyStateContextMock _applyContext; public ElectStateContextFacts() { _applyContext = new ApplyStateContextMock { OldStateName = "State" }; } [Fact] public void Ctor_ThrowsAnException_WhenApplyContextIsNull() { var exception = Assert.Throws( () => new ElectStateContext(null)); Assert.Equal("applyContext", exception.ParamName); } [Fact] public void Ctor_CorrectlySetAllProperties() { var context = CreateContext(); Assert.Same(_applyContext.Connection.Object, context.Connection); Assert.Same(_applyContext.Transaction.Object, context.Transaction); Assert.Same(_applyContext.BackgroundJob.Object, context.BackgroundJob); Assert.Same(_applyContext.NewState.Object, context.CandidateState); Assert.Equal("State", context.CurrentState); Assert.Empty(context.TraversedStates); Assert.Null(context.CustomData); } [Fact] public void Ctor_CopiesTheCustomDataDictionary_ToAnotherInstance() { // Arrange _applyContext.CustomData = new Dictionary { { "lalala", new object() } }; // Act var context = CreateContext(); // Assert Assert.Equal(_applyContext.CustomData, context.CustomData); Assert.NotSame(_applyContext.CustomData, context.CustomData); } [Fact] public void SetCandidateState_ThrowsAnException_WhenValueIsNull() { var context = CreateContext(); Assert.Throws(() => context.CandidateState = null); } [Fact] public void SetCandidateState_SetsTheGivenValue() { var context = CreateContext(); var newState = new Mock(); context.CandidateState = newState.Object; Assert.Same(newState.Object, context.CandidateState); } [Fact] public void SetCandidateState_AddsPreviousCandidateState_ToTraversedStatesList() { var context = CreateContext(); var state = new Mock(); context.CandidateState = state.Object; Assert.Contains(_applyContext.NewState.Object, context.TraversedStates); } [Fact] public void SetJobParameter_CallsTheCorrespondingMethod_WithJsonEncodedValue() { var context = CreateContext(); context.SetJobParameter("Name", "Value"); _applyContext.Connection.Verify(x => x.SetJobParameter( _applyContext.BackgroundJob.Id, "Name", JobHelper.ToJson("Value"))); } [Fact] public void SetJobParameter_CanReceiveNullValue() { var context = CreateContext(); context.SetJobParameter("Name", (string)null); _applyContext.Connection.Verify(x => x.SetJobParameter( _applyContext.BackgroundJob.Id, "Name", JobHelper.ToJson(null))); } [Fact] public void GetJobParameter_CallsTheCorrespondingMethod_WithJsonDecodedValue() { var context = CreateContext(); _applyContext.Connection.Setup(x => x.GetJobParameter(_applyContext.BackgroundJob.Id, "Name")) .Returns(JobHelper.ToJson("Value")); var value = context.GetJobParameter("Name"); Assert.Equal("Value", value); } [Fact] public void GetJobParameter_ReturnsDefaultValue_WhenNoValueProvided() { var context = CreateContext(); _applyContext.Connection.Setup(x => x.GetJobParameter("1", "Value")) .Returns(JobHelper.ToJson(null)); var value = context.GetJobParameter("Name"); Assert.Equal(default(int), value); } private ElectStateContext CreateContext() { return new ElectStateContext(_applyContext.Object); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/EnqueuedStateFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.States { public class EnqueuedStateFacts { [Fact] public void StateName_IsCorrect() { var state = new EnqueuedState(); Assert.Equal(EnqueuedState.StateName, state.Name); } [Fact] public void Ctor_ShouldSetQueue_WhenItWasGiven() { var state = new EnqueuedState("critical"); Assert.Equal("critical", state.Queue); } [Fact] public void SetQueue_ThrowsAnException_WhenQueueValueIsEmpty() { var state = new EnqueuedState(); Assert.Throws(() => state.Queue = String.Empty); } [Fact] public void SetQueue_ThrowsAnException_WhenValueIsNotInAGivenFormat() { var state = new EnqueuedState(); Assert.Throws(() => state.Queue = "UppercaseLetters"); Assert.Throws(() => state.Queue = "punctuation:un-allowed"); Assert.Throws(() => state.Queue = "моя_твоя_непонимать"); } [Fact] public void SetQueue_DoesNotThrowException_WhenValueIsInACorrectFormat() { var state = new EnqueuedState(); // Does not throw state.Queue = "lowercasedcharacters"; state.Queue = "underscores_allowed"; state.Queue = "1234567890_allowed"; } [Fact] public void SerializeData_ReturnsCorrectData() { var state = new EnqueuedState(); var serializedData = state.SerializeData(); Assert.Equal(state.Queue, serializedData["Queue"]); Assert.Equal(JobHelper.SerializeDateTime(state.EnqueuedAt), serializedData["EnqueuedAt"]); } [Fact] public void IsFinal_ReturnsFalse() { var state = new EnqueuedState(); Assert.False(state.IsFinal); } [Fact] public void IgnoreExceptions_ReturnsFalse() { var state = new EnqueuedState(); Assert.False(state.IgnoreJobLoadException); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_Before170() { var state = new EnqueuedState("default"); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\",\"Reason\":null}", serialized); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_After170() { var state = new EnqueuedState("default"); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\"}", serialized); } [DataCompatibilityRangeFact] public void JsonDeserialize_CanHandlePreviousFormat() { var json = "{\"Queue\":\"critical\",\"EnqueuedAt\":\"2012-04-02T11:22:33.0000000Z\",\"Name\":\"Enqueued\",\"Reason\":\"hello\",\"IsFinal\":false,\"IgnoreJobLoadException\":false}"; var state = SerializationHelper.Deserialize(json); Assert.Equal("critical", state.Queue); Assert.Equal("hello", state.Reason); } [DataCompatibilityRangeFact] public void JsonDeserialize_CanHandleNewFormat() { var json = "{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\",\"Queue\":\"default\"}"; var state = SerializationHelper.Deserialize(json); Assert.Equal("default", state.Queue); Assert.Null(state.Reason); } [DataCompatibilityRangeFact] public void JsonDeserialize_CanHandle170Beta1Format_WithDefaultValueForQueue() { var json = "{\"$type\":\"Hangfire.States.EnqueuedState, Hangfire.Core\"}"; var state = SerializationHelper.Deserialize(json); Assert.Equal("default", state.Queue); Assert.Null(state.Reason); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/EnqueuedStateHandlerFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.States { public class EnqueuedStateHandlerFacts { private const string Queue = "critical"; private readonly ApplyStateContextMock _context; private readonly Mock _transaction; private readonly EnqueuedState _enqueuedState; public EnqueuedStateHandlerFacts() { _enqueuedState = new EnqueuedState { Queue = Queue }; _context = new ApplyStateContextMock { NewStateObject = _enqueuedState }; _transaction = new Mock(); } [Fact] public void HandlerShouldBeRegistered_ForTheEnqueuedState() { var handler = new EnqueuedState.Handler(); Assert.Equal(EnqueuedState.StateName, handler.StateName); } [Fact] public void Apply_AddsJob_ToTheSpecifiedQueue() { var handler = new EnqueuedState.Handler(); handler.Apply(_context.Object, _transaction.Object); _transaction.Verify(x => x.AddToQueue(Queue, _context.BackgroundJob.Id)); } [Fact] public void Apply_ThrowsAnException_WhenOtherThanEnqueuedStateGiven() { var handler = new EnqueuedState.Handler(); _context.NewStateObject = null; _context.NewState = new Mock(); Assert.Throws( () => handler.Apply(_context.Object, _transaction.Object)); } [Fact] public void Apply_WithJobAndQueueSpecified_ThrowsAnException_WhenRequiredFeatureNotSupported() { _context.BackgroundJob.Job = Job.FromExpression(() => BackgroundJobMock.SomeMethod(), "critical"); var handler = new EnqueuedState.Handler(); Assert.Throws( () => handler.Apply(_context.Object, _transaction.Object)); } [Fact] public void Apply_AddsJob_ToTheJobTargetQueue_WhenEnqueuedState_HasTheDefaultQueue() { _context.Storage.Setup(x => x.HasFeature("Job.Queue")).Returns(true); _context.BackgroundJob.Job = Job.FromExpression(() => BackgroundJobMock.SomeMethod(), "myqueue"); _enqueuedState.Queue = "default"; var handler = new EnqueuedState.Handler(); handler.Apply(_context.Object, _transaction.Object); _transaction.Verify(x => x.AddToQueue("myqueue", _context.BackgroundJob.Id)); } [Fact] public void Apply_AddsJobToTheOverridenQueue_WhenTheJobTargetQueuePresent_ButEnqueuedStateQueueIsNotDefault() { _context.Storage.Setup(x => x.HasFeature("Job.Queue")).Returns(true); _context.BackgroundJob.Job = Job.FromExpression(() => BackgroundJobMock.SomeMethod(), "myqueue"); _enqueuedState.Queue = "otherqueue"; var handler = new EnqueuedState.Handler(); handler.Apply(_context.Object, _transaction.Object); _transaction.Verify(x => x.AddToQueue("otherqueue", _context.BackgroundJob.Id)); } [Fact] public void Unapply_DoesNotDoAnything() { var handler = new EnqueuedState.Handler(); // Does not throw handler.Unapply(null, null); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/EnqueuedStateValidationFacts.cs ================================================ using System; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.States { public class EnqueuedStateValidationFacts { [Fact] public void ValidateQueueName_ThrowsAnException_WhenQueueNameIsEmpty() { Assert.Throws(() => EnqueuedState.ValidateQueueName("queue", string.Empty)); } [Fact] public void ValidateQueueName_ThrowsAnException_WhenQueueNameIsNull() { Assert.Throws(() => EnqueuedState.ValidateQueueName("queue", null)); } [Fact] public void ValidateQueueName_ThrowsAnException_WhenQueueNameHasUpperCaseLetters() { Assert.Throws(() => EnqueuedState.ValidateQueueName("queue", "UppercaseLetters")); } [Fact] public void ValidateQueueName_ThrowsAnException_WhenQueueNameHasWhitespaces() { Assert.Throws(() => EnqueuedState.ValidateQueueName("queue", "test test")); } [Fact] public void ValidateQueueName_DoesntThrowAnException_WhenQueueNameHasOnlyLowerCaseLetters() { // Does not throw EnqueuedState.ValidateQueueName("queue", "valid"); } [Fact] public void ValidateQueueName_DoesntThrowAnException_WhenQueueNameHasUnderscores() { // Does not throw EnqueuedState.ValidateQueueName("queue", "a_b_c"); } [Fact] public void ValidateQueueName_DoesntThrowAnException_WhenValueHasOnlyDigits() { // Does not throw EnqueuedState.ValidateQueueName("queue", "363463"); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/FailedStateFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.States { public class FailedStateFacts { [Fact] public void Ctor_ThrowsAnException_IfExceptionParameterIsNull() { Assert.Throws( () => new FailedState(null)); } [Fact] public void StateName_IsCorrect() { var state = new FailedState(new Exception()); Assert.Equal(FailedState.StateName, state.Name); } [Fact] public void SerializeData_ReturnsCorrectData() { var state = new FailedState(new Exception("Message")); var serializedData = state.SerializeData(); Assert.Equal(JobHelper.SerializeDateTime(state.FailedAt), serializedData["FailedAt"]); Assert.Equal("System.Exception", serializedData["ExceptionType"]); Assert.Equal("Message", serializedData["ExceptionMessage"]); Assert.Equal(state.Exception.ToString(), serializedData["ExceptionDetails"]); } [Fact] public void IsFinal_ReturnsFalse() { var state = new FailedState(new Exception()); Assert.False(state.IsFinal); } [Fact] public void IgnoreExceptions_ReturnsFalse() { var state = new FailedState(new Exception()); Assert.False(state.IgnoreJobLoadException); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/ProcessingStateFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.States { public class ProcessingStateFacts { private const string WorkerId = "1"; private const string ServerId = "Server1:4231"; [Fact] public void Ctor_ThrowsAnException_WhenServerNameIsNull() { Assert.Throws( () => new ProcessingState(null, WorkerId)); } [Fact] public void Ctor_ThrowsAnException_WhenServerNameIsEmpty() { Assert.Throws( () => new ProcessingState(String.Empty, WorkerId)); } [Fact] public void StateName_IsCorrect() { var state = CreateState(); Assert.Equal(ProcessingState.StateName, state.Name); } [Fact] public void SerializeData_ReturnsCorrectData() { var state = CreateState(); var data = state.SerializeData(); Assert.Equal(JobHelper.SerializeDateTime(state.StartedAt), data["StartedAt"]); Assert.Equal(ServerId, data["ServerId"]); Assert.Equal(WorkerId.ToString(), data["WorkerId"]); } [Fact] public void IsFinal_ReturnsFalse() { var state = CreateState(); Assert.False(state.IsFinal); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_Before170() { var state = new ProcessingState("server1", "worker1"); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.ProcessingState, Hangfire.Core\",\"ServerId\":\"server1\"," + "\"WorkerId\":\"worker1\",\"Reason\":null}", serialized); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_After170() { var state = new ProcessingState("server1", "worker1"); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.ProcessingState, Hangfire.Core\",\"ServerId\":\"server1\"," + "\"WorkerId\":\"worker1\"}", serialized); } [Fact] public void IgnoreExceptions_ReturnsFalse() { var state = CreateState(); Assert.False(state.IgnoreJobLoadException); } private ProcessingState CreateState() { return new ProcessingState(ServerId, WorkerId); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/ScheduledStateFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Newtonsoft.Json; using Xunit; namespace Hangfire.Core.Tests.States { public class ScheduledStateFacts { [Fact] public void StateName_IsCorrect() { var state = new ScheduledState(DateTime.UtcNow); Assert.Equal(ScheduledState.StateName, state.Name); } [Fact] public void Ctor_SetsTheCorrectData_WhenDateIsPassed() { var date = new DateTime(2012, 12, 12); var state = new ScheduledState(date); Assert.Equal(date, state.EnqueueAt); } [Fact] public void Ctor_SetsTheCorrectDate_WhenTimeSpanIsPassed() { var state = new ScheduledState(TimeSpan.FromDays(1)); Assert.True(DateTime.UtcNow.AddDays(1).AddMinutes(-1) < state.EnqueueAt); Assert.True(state.EnqueueAt < DateTime.UtcNow.AddDays(1).AddMinutes(1)); } [Fact] public void SerializeData_ReturnsCorrectData() { var state = new ScheduledState(new DateTime(2012, 12, 12)); var data = state.SerializeData(); Assert.Equal(JobHelper.SerializeDateTime(state.EnqueueAt), data["EnqueueAt"]); Assert.Equal(JobHelper.SerializeDateTime(state.ScheduledAt), data["ScheduledAt"]); } [Fact] public void SerializeData_DoesNotContainQueueKey_WhenItIsNotSet() { var state = new ScheduledState(TimeSpan.Zero); var data = state.SerializeData(); Assert.DoesNotContain("Queue", data.Keys); } [Fact] public void IsFinal_ReturnsFalse() { var state = new ScheduledState(DateTime.UtcNow); Assert.False(state.IsFinal); } [Fact] public void IgnoreExceptions_ReturnsFalse() { var state = new ScheduledState(DateTime.UtcNow); Assert.False(state.IgnoreJobLoadException); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_Before170() { var dateTime = DateTime.UtcNow; var state = new ScheduledState(dateTime); var convertedDateTime = JsonConvert.SerializeObject(dateTime); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.ScheduledState, Hangfire.Core\",\"EnqueueAt\":" + convertedDateTime + ",\"Reason\":null}", serialized); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_After170() { var dateTime = DateTime.UtcNow; var state = new ScheduledState(dateTime); var convertedDateTime = JsonConvert.SerializeObject(dateTime); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.ScheduledState, Hangfire.Core\",\"EnqueueAt\":" + convertedDateTime + "}", serialized); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/ScheduledStateHandlerFacts.cs ================================================ using System; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.States { public class ScheduledStateHandlerFacts { private readonly ApplyStateContextMock _context; private readonly Mock _transaction; private const string JobId = "1"; private readonly DateTime _enqueueAt = DateTime.UtcNow.AddDays(1); public ScheduledStateHandlerFacts() { _context = new ApplyStateContextMock { BackgroundJob = { Id = JobId }, NewStateObject = new ScheduledState(_enqueueAt) }; _transaction = new Mock(); } [Fact] public void StateName_ShouldBeEqualToScheduledState() { var handler = new ScheduledState.Handler(); Assert.Equal(ScheduledState.StateName, handler.StateName); } [Fact] public void Apply_ShouldAddTheJob_ToTheScheduleSet_WithTheCorrectScore() { var handler = new ScheduledState.Handler(); handler.Apply(_context.Object, _transaction.Object); _transaction.Verify(x => x.AddToSet( "schedule", JobId, JobHelper.ToTimestamp(_enqueueAt))); } [Fact] public void Apply_WithJobAndQueueSpecified_ThrowsAnException_WhenRequiredFeatureNotSupported() { _context.BackgroundJob.Job = Job.FromExpression(() => BackgroundJobMock.SomeMethod(), "critical"); var handler = new ScheduledState.Handler(); Assert.Throws( () => handler.Apply(_context.Object, _transaction.Object)); } [Fact] public void Apply_ShouldAddJob_WithQueueSpecified_ToTheScheduleSet_WithQueuePrepended() { _context.Storage.Setup(x => x.HasFeature("Job.Queue")).Returns(true); _context.BackgroundJob.Job = Job.FromExpression(() => BackgroundJobMock.SomeMethod(), "critical"); var handler = new ScheduledState.Handler(); handler.Apply(_context.Object, _transaction.Object); _transaction.Verify(x => x.AddToSet( "schedule", "critical:" + JobId, It.IsAny())); } [Fact] public void Unapply_ShouldRemoveTheJobId_FromTheScheduledSet() { var handler = new ScheduledState.Handler(); handler.Unapply(_context.Object, _transaction.Object); _transaction.Verify(x => x.RemoveFromSet("schedule", JobId)); } [Fact] public void Unapply_WithJobAndQueueSpecified_ThrowsAnException_WhenRequiredFeatureNotSupported() { _context.BackgroundJob.Job = Job.FromExpression(() => BackgroundJobMock.SomeMethod(), "critical"); var handler = new ScheduledState.Handler(); Assert.Throws( () => handler.Unapply(_context.Object, _transaction.Object)); } [Fact] public void Unapply_WithJob_WithQueueSpecified_ShouldRemoveTheJobId_FromTheScheduleSet_PrependedWithQueueName() { _context.Storage.Setup(x => x.HasFeature("Job.Queue")).Returns(true); _context.BackgroundJob.Job = Job.FromExpression(() => BackgroundJobMock.SomeMethod(), "critical"); var handler = new ScheduledState.Handler(); handler.Unapply(_context.Object, _transaction.Object); _transaction.Verify(x => x.RemoveFromSet("schedule", $"critical:{JobId}")); } [Fact] public void Apply_ThrowsAnException_WhenGivenStateIsNotScheduledState() { var handler = new ScheduledState.Handler(); _context.NewStateObject = null; _context.NewState = new Mock(); Assert.Throws( () => handler.Apply(_context.Object, _transaction.Object)); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/StateChangeContextFacts.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using Hangfire.Profiling; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.States { public class StateChangeContextFacts { private const string JobId = "SomeJob"; private readonly Mock _storage; private readonly Mock _connection; private readonly Mock _newState; private readonly string[] _expectedStates; private readonly CancellationToken _token; private readonly Mock _profiler; private readonly Dictionary _customData; public StateChangeContextFacts() { _storage = new Mock(); _connection = new Mock(); _newState = new Mock(); _expectedStates = new[] { "Succeeded", "Failed" }; _token = new CancellationToken(true); _profiler = new Mock(); _customData = new Dictionary(); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new StateChangeContext( null, _connection.Object, JobId, _newState.Object)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenConnectionIsNull() { var exception = Assert.Throws( () => new StateChangeContext( _storage.Object, null, JobId, _newState.Object)); Assert.Equal("connection", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenBackgroundJobIdIsNull() { var exception = Assert.Throws( () => new StateChangeContext( _storage.Object, _connection.Object, null, _newState.Object)); Assert.Equal("backgroundJobId", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenNewStateIsNull() { var exception = Assert.Throws( () => new StateChangeContext( _storage.Object, _connection.Object, JobId, null)); Assert.Equal("newState", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenProfilerIsNull() { var exception = Assert.Throws( () => new StateChangeContext( _storage.Object, _connection.Object, JobId, _newState.Object, null, false, CancellationToken.None, null, null)); Assert.Equal("profiler", exception.ParamName); } [Fact] public void Ctor1_CorrectlySets_AllTheProperties() { var context = new StateChangeContext( _storage.Object, _connection.Object, JobId, _newState.Object); Assert.Same(_storage.Object, context.Storage); Assert.Same(_connection.Object, context.Connection); Assert.Equal(JobId, context.BackgroundJobId); Assert.Same(_newState.Object, context.NewState); Assert.Null(context.ExpectedStates); Assert.Equal(CancellationToken.None, context.CancellationToken); Assert.NotNull(context.Profiler); Assert.Null(context.CustomData); } [Fact] public void Ctor2_CorrectlySets_AllTheProperties() { var context = new StateChangeContext( _storage.Object, _connection.Object, JobId, _newState.Object, _expectedStates); Assert.Same(_storage.Object, context.Storage); Assert.Same(_connection.Object, context.Connection); Assert.Equal(JobId, context.BackgroundJobId); Assert.Same(_newState.Object, context.NewState); Assert.Equal(_expectedStates, context.ExpectedStates); Assert.Equal(CancellationToken.None, context.CancellationToken); Assert.NotNull(context.Profiler); Assert.Null(context.CustomData); } [Fact] public void Ctor3_CorrectlySets_AllTheProperties() { var context = new StateChangeContext( _storage.Object, _connection.Object, JobId, _newState.Object, _expectedStates, _token); Assert.Same(_storage.Object, context.Storage); Assert.Same(_connection.Object, context.Connection); Assert.Equal(JobId, context.BackgroundJobId); Assert.Same(_newState.Object, context.NewState); Assert.Equal(_expectedStates, context.ExpectedStates); Assert.Equal(_token, context.CancellationToken); Assert.NotNull(context.Profiler); Assert.Null(context.CustomData); } [Fact] public void InternalCtor_CorrectlySets_AllTheProperties() { var context = new StateChangeContext( _storage.Object, _connection.Object, JobId, _newState.Object, _expectedStates, false, _token, _profiler.Object, "some-server", _customData); Assert.Same(_storage.Object, context.Storage); Assert.Same(_connection.Object, context.Connection); Assert.Equal(JobId, context.BackgroundJobId); Assert.Same(_newState.Object, context.NewState); Assert.Equal(_expectedStates, context.ExpectedStates); Assert.False(context.DisableFilters); Assert.Equal(_token, context.CancellationToken); Assert.Same(_profiler.Object, context.Profiler); Assert.Equal("some-server", context.ServerId); Assert.Same(_customData, context.CustomData); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/StateHandlerCollectionFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Hangfire.States; using Moq; using Xunit; namespace Hangfire.Core.Tests.States { public class StateHandlerCollectionFacts { private readonly StateHandlerCollection _collection; public StateHandlerCollectionFacts() { _collection = new StateHandlerCollection(); } [Fact] public void AddHandler_ThrowsAnException_WhenHandlerIsNull() { Assert.Throws( () => _collection.AddHandler(null)); } [Fact] public void AddHandler_ThrowsAnException_WhenStateNameOfTheGivenHandlerIsNull() { var handler = new Mock(); handler.Setup(x => x.StateName).Returns((string)null); var exception = Assert.Throws( () => _collection.AddHandler(handler.Object)); Assert.Contains("StateName", exception.Message); } [Fact] public void GetHandlers_ReturnsEmptyCollection_WhenHandlersWereNotAddedForTheState() { var handlers = _collection.GetHandlers("State"); Assert.Empty(handlers); } [Fact] public void GetHandlers_ReturnsEmptyCollection_WhenStateNameIsNull() { var handlers = _collection.GetHandlers(null); Assert.Empty(handlers); } [Fact] public void GetHandlers_ReturnsAllRegisteredHandlersForTheState() { var handler1Mock = new Mock(); handler1Mock.Setup(x => x.StateName).Returns("State"); var handler2Mock = new Mock(); handler2Mock.Setup(x => x.StateName).Returns("State"); _collection.AddHandler(handler1Mock.Object); _collection.AddHandler(handler2Mock.Object); var handlers = _collection.GetHandlers("State").ToArray(); Assert.Contains(handler1Mock.Object, handlers); Assert.Contains(handler2Mock.Object, handlers); } [Fact] public void GetHandlers_ReturnsOnlyHandlersOfASpecifiedState() { var anotherStateHandlerMock = new Mock(); anotherStateHandlerMock.Setup(x => x.StateName).Returns("AnotherState"); _collection.AddHandler(anotherStateHandlerMock.Object); var handlers = _collection.GetHandlers("State"); Assert.Empty(handlers); } [Fact] public void AddRange_ThrowsAnException_WhenEnumerationIsNull() { Assert.Throws( () => _collection.AddRange(null)); } [Fact] public void AddRange_AddsHandlers_FromEnumeration() { // Arrange var handler1 = new Mock(); handler1.Setup(x => x.StateName).Returns("State1"); var handler2 = new Mock(); handler2.Setup(x => x.StateName).Returns("State2"); var handlers = new List { handler1.Object, handler2.Object }; // Act _collection.AddRange(handlers); // Assert Assert.Same(handler1.Object, _collection.GetHandlers("State1").Single()); Assert.Same(handler2.Object, _collection.GetHandlers("State2").Single()); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/StateMachineFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Hangfire.Common; using Hangfire.States; using Hangfire.Storage; using Moq; using Moq.Sequences; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.Core.Tests.States { public class StateMachineFacts { private const string OldStateName = "OldState"; private const string JobId = "job"; private readonly List _filters = new List(); private readonly Mock _transaction; private readonly ApplyStateContextMock _context; private readonly Mock _filterProvider; private readonly Mock _innerMachine; public StateMachineFacts() { var connection = new Mock(); _transaction = new Mock(); connection.Setup(x => x.CreateWriteTransaction()).Returns(_transaction.Object); var backgroundJob = new BackgroundJobMock { Id = JobId }; _context = new ApplyStateContextMock { BackgroundJob = backgroundJob, OldStateName = OldStateName, Transaction = _transaction }; _filterProvider = new Mock(); _filterProvider.Setup(x => x.GetFilters(It.IsNotNull())).Returns( _filters.Select(f => new JobFilter(f, JobFilterScope.Type, null))); _innerMachine = new Mock(); } [Fact] public void Ctor_ThrowsAnException_WhenFilterProviderIsNull() { var exception = Assert.Throws( () => new StateMachine(null, _innerMachine.Object)); Assert.Equal("filterProvider", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenInnerStateMachineIsNull() { var exception = Assert.Throws( () => new StateMachine(_filterProvider.Object, null)); Assert.Equal("innerStateMachine", exception.ParamName); } [Fact] public void ApplyState_CallsElectionFilterWithCorrectProperties() { // Arrange var filter = CreateFilter(); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_context.Object); filter.Verify(x => x.OnStateElection(It.Is(context => context.Storage == _context.Storage.Object && context.Connection == _context.Connection.Object && context.BackgroundJob == _context.BackgroundJob.Object && context.CandidateState == _context.NewState.Object && context.CurrentState == _context.OldStateName))); } [Fact, SequenceAttribute] public void ApplyState_CallsElectionFilters() { // Arrange var filter1 = CreateFilter(); var filter2 = CreateFilter(); filter1.Setup(x => x.OnStateElection(It.IsAny())) .InSequence(); filter2.Setup(x => x.OnStateElection(It.IsAny())) .InSequence(); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_context.Object); // Assert - Sequence } [Fact] public void ApplyState_AddsJobHistory_ForTraversedStates() { // Arrange var anotherState = new Mock(); var filter = CreateFilter(); filter.Setup(x => x.OnStateElection(It.IsNotNull())) .Callback(context => context.CandidateState = anotherState.Object); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_context.Object); // Assert _context.Transaction.Verify(x => x.AddJobState(JobId, _context.NewState.Object)); } [Fact, Sequence] public void ApplyState_CallsStateUnappliedFilters_BeforeCallingInnerStateMachine() { // Arrange var filter1 = CreateFilter(); var filter2 = CreateFilter(); filter1.Setup(x => x.OnStateUnapplied(It.IsNotNull(), _transaction.Object)) .InSequence(); filter2.Setup(x => x.OnStateUnapplied(It.IsNotNull(), _transaction.Object)) .InSequence(); _innerMachine .Setup(x => x.ApplyState(It.IsAny())) .InSequence(); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_context.Object); // Assert - Sequence } [Fact, Sequence] public void ApplyState_CallsStateAppliedFilters_AfterSettingTheState() { // Arrange var filter1 = CreateFilter(); var filter2 = CreateFilter(); filter1.Setup(x => x.OnStateApplied(It.IsNotNull(), _transaction.Object)) .InSequence(); filter2.Setup(x => x.OnStateApplied(It.IsNotNull(), _transaction.Object)) .InSequence(); var stateMachine = CreateStateMachine(); // Act stateMachine.ApplyState(_context.Object); // Assert - Sequence } private StateMachine CreateStateMachine() { return new StateMachine(_filterProvider.Object, _innerMachine.Object); } private Mock CreateFilter() where T : class { var filter = new Mock(); _filters.Add(filter.Object); return filter; } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/SucceededStateFacts.cs ================================================ using System.Diagnostics; using Hangfire.Common; using Hangfire.States; using Xunit; namespace Hangfire.Core.Tests.States { public class SucceededStateFacts { [Fact] public void StateName_IsEqualToSucceeded() { Assert.Equal("Succeeded", SucceededState.StateName); } [Fact] public void NameProperty_ReturnsStateName() { var state = CreateState(); Assert.Equal(SucceededState.StateName, state.Name); } [Fact] public void SerializeData_ReturnsCorrectData() { var state = CreateState(); var data = state.SerializeData(); Assert.Equal("\"Returned value\"", data["Result"]); Assert.Equal(JobHelper.SerializeDateTime(state.SucceededAt), data["SucceededAt"]); Assert.Equal("123", data["PerformanceDuration"]); Assert.Equal("11", data["Latency"]); } [Fact] public void SerializeData_DoesNotAppendEntry_ForNullResult() { var state = new SucceededState(null, 0, 0); var data = state.SerializeData(); Assert.False(data.ContainsKey("Result")); } [Fact] public void SerializeData_CorrectlyHandlesResults_ThatCantBeSerialized() { var process = new Process(); var state = new SucceededState(process, 0, 0); var data = state.SerializeData(); Assert.Contains("Can not serialize", data["Result"]); } [Fact] public void IsFinal_ReturnsTrue() { var state = CreateState(); Assert.True(state.IsFinal); } [Fact] public void IgnoreExceptions_ReturnsFalse() { var state = CreateState(); Assert.False(state.IgnoreJobLoadException); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_Before170() { var state = new SucceededState(null, 1, 2); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.SucceededState, Hangfire.Core\",\"Result\":null,\"Latency\":1,\"PerformanceDuration\":2,\"Reason\":null}", serialized); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void JsonSerialize_ReturnsCorrectString_After170() { var state = new SucceededState(null, 1, 2); var serialized = SerializationHelper.Serialize(state, SerializationOption.TypedInternal); Assert.Equal( "{\"$type\":\"Hangfire.States.SucceededState, Hangfire.Core\",\"Latency\":1,\"PerformanceDuration\":2}", serialized); } private static SucceededState CreateState() { return new SucceededState("Returned value", 11, 123); } } } ================================================ FILE: tests/Hangfire.Core.Tests/States/SucceededStateHandlerFacts.cs ================================================ using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.States { public class SucceededStateHandlerFacts { private readonly ApplyStateContextMock _context; private readonly Mock _transactionMock = new Mock(); public SucceededStateHandlerFacts() { _context = new ApplyStateContextMock(); } [Fact] public void ShouldWorkOnlyWithSucceededState() { var handler = new SucceededState.Handler(); Assert.Equal(SucceededState.StateName, handler.StateName); } [Fact] public void Apply_ShouldIncrease_SucceededCounter() { var handler = new SucceededState.Handler(); handler.Apply(_context.Object, _transactionMock.Object); _transactionMock.Verify(x => x.IncrementCounter("stats:succeeded"), Times.Once); } [Fact] public void Unapply_ShouldDecrementStatistics() { var handler = new SucceededState.Handler(); handler.Unapply(_context.Object, _transactionMock.Object); _transactionMock.Verify(x => x.DecrementCounter("stats:succeeded"), Times.Once); } } } ================================================ FILE: tests/Hangfire.Core.Tests/StatisticsHistoryAttributeFacts.cs ================================================ using System; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests { public class StatisticsHistoryAttributeFacts { private readonly Mock _connection; private readonly StatisticsHistoryAttribute _filter; private readonly Mock _transaction; private readonly ElectStateContextMock _context; public StatisticsHistoryAttributeFacts() { _connection = new Mock(); _transaction = new Mock(); _connection.Setup(x => x.CreateWriteTransaction()).Returns(_transaction.Object); _filter = new StatisticsHistoryAttribute(); _context = new ElectStateContextMock { ApplyContext = { Connection = _connection, NewStateObject = new SucceededState(null, 11, 123), Transaction = _transaction } }; } [Fact] public void StatisticsHistoryFilter_ActsBefore_RetryFilter() { var statisticsHistoryFilter = new StatisticsHistoryAttribute(); var retryFilter = new AutomaticRetryAttribute(); Assert.True(statisticsHistoryFilter.Order > retryFilter.Order); } [Fact] public void OnStateElection_IncrementsCounters_ForSucceededState() { _filter.OnStateElection(_context.Object); VerifyCountersIncremented("stats:succeeded:"); } [Fact] public void OnStateElection_IncrementsCounters_ForFailedState() { _context.ApplyContext.NewStateObject = new FailedState(new InvalidOperationException()); _filter.OnStateElection(_context.Object); VerifyCountersIncremented("stats:failed:"); } [Fact] public void OnStateElection_DoesNotCreateTransaction_ForUnsuitableState() { _context.ApplyContext.NewStateObject = new ProcessingState("server", "1"); _filter.OnStateElection(_context.Object); _connection.Verify(x => x.CreateWriteTransaction(), Times.Never); } private void VerifyCountersIncremented(string prefix) { var thisDay = DateTime.UtcNow.ToString("yyyy-MM-dd"); var prevDay = DateTime.UtcNow.AddDays(-1).ToString("yyyy-MM-dd"); var thisHour = DateTime.UtcNow.ToString("yyyy-MM-dd-HH"); var prevHour = DateTime.UtcNow.AddHours(1).ToString("yyyy-MM-dd-HH"); _transaction.Verify(x => x.IncrementCounter( It.Is(key => key == prefix + thisDay || key == prefix + prevDay), It.Is(expire => expire.TotalDays >= 27))); _transaction.Verify(x => x.IncrementCounter( It.Is(date => date == prefix + thisHour || date == prefix + prevHour), TimeSpan.FromDays(1))); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Storage/InvocationDataFacts.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Hangfire.Common; using Hangfire.Storage; using Newtonsoft.Json; using Xunit; using System.Globalization; using System.Linq; using System.Threading; using Hangfire.Annotations; using Newtonsoft.Json.Serialization; #pragma warning disable 618 namespace Hangfire.Core.Tests.Storage { public class InvocationDataFacts { [Fact] public void Ctor_InitializesAllTheGivenProperties() { var invocationData = new InvocationData("type", "method", "parameterTypes", "arguments"); Assert.Equal("type", invocationData.Type); Assert.Equal("method", invocationData.Method); Assert.Equal("parameterTypes", invocationData.ParameterTypes); Assert.Equal("arguments", invocationData.Arguments); Assert.Null(invocationData.Queue); } [Fact] public void Ctor_WithQueue_InitializesAllTheGivenProperties() { var invocationData = new InvocationData("type", "method", "parameterTypes", "arguments", "critical"); Assert.Equal("type", invocationData.Type); Assert.Equal("method", invocationData.Method); Assert.Equal("parameterTypes", invocationData.ParameterTypes); Assert.Equal("arguments", invocationData.Arguments); Assert.Equal("critical", invocationData.Queue); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_AllTheData() { var type = typeof(InvocationDataFacts); var methodInfo = type.GetMethod("Sample"); var serializedData = new InvocationData( type.AssemblyQualifiedName, // ReSharper disable once PossibleNullReferenceException methodInfo.Name, JobHelper.ToJson(new [] { typeof(string) }), JobHelper.ToJson(new [] { JobHelper.ToJson("Hello") })); var job = serializedData.Deserialize(); Assert.Equal(type, job.Type); Assert.Equal(methodInfo, job.Method); Assert.Equal("Hello", job.Args[0]); Assert.Null(job.Queue); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_QueueProperty() { var serializedData = new InvocationData( "Hangfire.JobStorage, Hangfire.Core", "GetConnection", String.Empty, null, "critical"); var job = serializedData.Deserialize(); Assert.Equal("critical", job.Queue); } [DataCompatibilityRangeFact] public void Deserialize_HandlesNullOrEmpty_ParameterTypesAndArguments() { var serializedData = new InvocationData( "Hangfire.JobStorage, Hangfire.Core", "GetConnection", String.Empty, null); var job = serializedData.Deserialize(); Assert.Equal(typeof(JobStorage), job.Type); } [DataCompatibilityRangeFact] public void Deserialize_HandlesTypesWithoutAssemblyName_FromMscorlibAssembly() { var serializedData = new InvocationData( "System.DateTime", "IsLeapYear", "[\"System.Int32\"]", "[\"1\"]"); var job = serializedData.Deserialize(); Assert.Equal(typeof(DateTime), job.Type); } [DataCompatibilityRangeFact] public void Deserialize_HandlesPartialAssemblyNames() { var serializedData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Empty", null, null); var job = serializedData.Deserialize(); Assert.Equal(typeof(InvocationDataFacts), job.Type); } [DataCompatibilityRangeFact] public void Deserialize_HandlesFullAssemblyNames() { var serializedData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Empty", null, null); var job = serializedData.Deserialize(); Assert.Equal(typeof(InvocationDataFacts), job.Type); } [DataCompatibilityRangeFact] public void Deserialize_HandlesFullyQualifiedAssemblyNames_OfNonSignedAssembly_OfDifferentVersion() { try { GlobalConfiguration.Configuration.UseIgnoredAssemblyVersionTypeResolver(); var serializedData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=9.9.9.9, Culture=neutral, PublicKeyToken=null", "Empty", null, null); var job = serializedData.Deserialize(); Assert.Equal(typeof(InvocationDataFacts), job.Type); } finally { GlobalConfiguration.Configuration.UseDefaultTypeResolver(); } } [DataCompatibilityRangeFact] public void Deserialize_HandlesFullyQualifiedAssemblyNames_OfSignedAssembly_OfDifferentVersion() { try { GlobalConfiguration.Configuration.UseIgnoredAssemblyVersionTypeResolver(); var serializedData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=9.9.9.9, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", "Empty", null, null); var job = serializedData.Deserialize(); Assert.Equal(typeof(InvocationDataFacts), job.Type); } finally { GlobalConfiguration.Configuration.UseDefaultTypeResolver(); } } [DataCompatibilityRangeFact] public void Deserialize_HandlesGenericTypes_WithFullyQualifiedAssemblyNames_OfSignedAssembly_OfDifferentVersion() { try { GlobalConfiguration.Configuration.UseIgnoredAssemblyVersionTypeResolver(); var serializedData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts+GenericType`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Hangfire.Core.Tests, Version=9.9.9.9, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", "Method", "[\"System.Int32, System.Private.CoreLib, Version=9.9.9.9, Culture=neutral, PublicKeyToken=lalalalala\",\"System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e\"]", "[\"123\",\"456\"]"); var job = serializedData.Deserialize(); Assert.Equal(typeof(GenericType), job.Type); Assert.Equal("Method", job.Method.Name); Assert.Equal(123, job.Args[0]); } finally { GlobalConfiguration.Configuration.UseDefaultTypeResolver(); } } [DataCompatibilityRangeFact] public void Deserialize_HandlesSystemPrivateCoreLib_TypeForwarding() { var serializedData = new InvocationData( "System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", "IsNullOrEmpty", "[\"System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e\"]", JobHelper.ToJson(new[] { JobHelper.ToJson("hello") })); var job = serializedData.Deserialize(); Assert.Equal(typeof(string), job.Type); Assert.Equal("IsNullOrEmpty", job.Method.Name); Assert.Equal("hello", job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_WrapsAnException_WithTheJobLoadException() { var serializedData = new InvocationData(null, null, null, null); Assert.Throws( () => serializedData.Deserialize()); } [DataCompatibilityRangeFact] public void Deserialize_ThrowsAnException_WhenTypeCanNotBeFound() { var serializedData = new InvocationData( "NonExistingType", "Perform", "", ""); Assert.Throws( () => serializedData.Deserialize()); } [DataCompatibilityRangeFact] public void Deserialize_ThrowsAnException_WhenMethodCanNotBeFound() { var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, "NonExistingMethod", JobHelper.ToJson(new [] { typeof(string) }), ""); Assert.Throws( () => serializedData.Deserialize()); } [DataCompatibilityRangeFact] public void Serialize_CorrectlySerializesTheData() { var job = Job.FromExpression(() => Sample("Hello")); var invocationData = InvocationData.Serialize(job); Assert.Equal(typeof(InvocationDataFacts).AssemblyQualifiedName, invocationData.Type); Assert.Equal("Sample", invocationData.Method); Assert.Equal(JobHelper.ToJson(new[] { typeof(string) }), invocationData.ParameterTypes); Assert.Equal(JobHelper.ToJson(new[] { "\"Hello\"" }), invocationData.Arguments); Assert.Null(invocationData.Queue); } [DataCompatibilityRangeFact] public void Serialize_CorrectlySerializes_TheJobQueueProperty() { var job = Job.FromExpression(() => Sample("Hello"), "critical"); var invocationData = InvocationData.Serialize(job); Assert.Equal("Sample", invocationData.Method); Assert.Equal("critical", invocationData.Queue); } [DataCompatibilityRangeFact] public void Serialize_CorrectlyHandles_ParameterTypes_InPossibleOldFormat() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "ComplicatedMethod", "{\"$type\":\"System.Type[], mscorlib\",\"$values\":[\"System.Collections.Generic.IList`1[[System.String, mscorlib]], mscorlib\",\"Hangfire.Core.Tests.Storage.InvocationDataFacts+SomeClass, Hangfire.Core.Tests\"]}", "[null, null]"); var serialized = invocationData.SerializePayload(); var job = InvocationData.DeserializePayload(serialized).Deserialize(); Assert.Equal(typeof(InvocationDataFacts), job.Type); Assert.Equal(typeof(InvocationDataFacts).GetMethod("ComplicatedMethod"), job.Method); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void Serialize_SerializesDateTimeUsingCustomFormatter_BeforeVersion170() { var dateTimeString = "2019-03-05T13:20:04.5932150Z"; var dateTime = DateTime.Parse(dateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); var dateTimeData = InvocationData.SerializeJob(Job.FromExpression(() => DateTimeMethod(dateTime))); var nullableData = InvocationData.SerializeJob(Job.FromExpression(() => NullableDateTimeMethod(dateTime))); Assert.Equal($"[\"{dateTimeString}\"]", dateTimeData.Arguments); Assert.Equal($"[\"{dateTimeString}\"]", nullableData.Arguments); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void Serialize_SerializesDateTimeUsingRegularJsonFormatter_AfterVersion170() { var dateTimeString = "\"2019-03-05T13:20:04.5932150Z\""; var dateTime = SerializationHelper.Deserialize(dateTimeString, SerializationOption.User); var dateTimeData = InvocationData.SerializeJob(Job.FromExpression(() => DateTimeMethod(dateTime))); var nullableData = InvocationData.SerializeJob(Job.FromExpression(() => NullableDateTimeMethod(dateTime))); Assert.Equal($"[\"\\\"2019-03-05T13:20:04.593215Z\\\"\"]", dateTimeData.Arguments); Assert.Equal($"[\"\\\"2019-03-05T13:20:04.593215Z\\\"\"]", nullableData.Arguments); } [DataCompatibilityRangeFact(), CleanSerializerSettings] public void Serialize_WithTypeNameHandlingAuto_PreservesTypeInformation() { JobHelper.SetSerializerSettings(new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, TypeNameHandling = TypeNameHandling.Auto }); var job = Job.FromExpression(() => GenericMethod(new SomeClass())); var data = InvocationData.SerializeJob(job); Assert.Equal("[\"{\\\"$type\\\":\\\"Hangfire.Core.Tests.Storage.InvocationDataFacts+SomeClass, Hangfire.Core.Tests\\\"}\"]", data.Arguments); } [DataCompatibilityRangeFact, CleanSerializerSettings] public void Deserialize_CanHandleArgumentWithExplicitTypeName_WhenUsingTypeNameHandlingAuto() { JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); var data = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "GenericMethod", "[\"System.Object, mscorlib\"]", "[\"{\\\"$type\\\":\\\"Hangfire.Core.Tests.Storage.InvocationDataFacts+SomeClass, Hangfire.Core.Tests\\\"}\"]"); var job = data.DeserializeJob(); Assert.Equal("GenericMethod", job.Method.Name); Assert.Equal(new object[] { typeof(object) }, job.Method.GetParameters().Select(x => x.ParameterType).ToArray()); Assert.IsType(job.Args[0]); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void SerializePayload_CorrectlySerializesInvocationDataToString_WithOldFormat_InVersion_Pre_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Sample", "[\"System.String\"]", "[\"\\\"Hello\\\"\"]"); var payload = invocationData.SerializePayload(); Assert.Equal( "{\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"Method\":\"Sample\",\"ParameterTypes\":\"[\\\"System.String\\\"]\",\"Arguments\":\"[\\\"\\\\\\\"Hello\\\\\\\"\\\"]\"}", payload); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void SerializePayload_CorrectlySerializesInvocationDataWithQueueToString_WithOldFormat_InVersion_Pre_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Sample", "[\"System.String\"]", "[\"\\\"Hello\\\"\"]", "critical"); var payload = invocationData.SerializePayload(); Assert.Equal( "{\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"Method\":\"Sample\",\"ParameterTypes\":\"[\\\"System.String\\\"]\",\"Arguments\":\"[\\\"\\\\\\\"Hello\\\\\\\"\\\"]\",\"Queue\":\"critical\"}", payload); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void SerializePayload_DoesNotIncludeArgumentsWhenStatedSo_WithOldFormat_InVersion_Pre_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Sample", "[\"System.String\"]", "[\"\\\"Hello\\\"\"]"); var payload = invocationData.SerializePayload(excludeArguments: true); Assert.Equal( "{\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"Method\":\"Sample\",\"ParameterTypes\":\"[\\\"System.String\\\"]\",\"Arguments\":null}", payload); } [DataCompatibilityRangeFact(MaxExcludingLevel = CompatibilityLevel.Version_170)] public void SerializePayload_SerializesInvocationDataToString_WithoutNullifyingEmptyEntries_InVersion_Pre_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Empty", "[]", "[]"); var payload = invocationData.SerializePayload(); Assert.Equal( "{\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"Method\":\"Empty\",\"ParameterTypes\":\"[]\",\"Arguments\":\"[]\"}", payload); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializePayload_CorrectlySerializesInvocationDataToString_WithNewFormat_InVersion_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Sample", "[\"System.String\"]", "[\"\\\"Hello\\\"\"]"); var payload = invocationData.SerializePayload(); Assert.Equal( "{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"Sample\",\"p\":[\"System.String\"],\"a\":[\"\\\"Hello\\\"\"]}", payload); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializePayload_CorrectlySerializesInvocationDataWithQueueToString_WithNewFormat_InVersion_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Sample", "[\"System.String\"]", "[\"\\\"Hello\\\"\"]", "critical"); var payload = invocationData.SerializePayload(); Assert.Equal( "{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"Sample\",\"p\":[\"System.String\"],\"a\":[\"\\\"Hello\\\"\"],\"q\":\"critical\"}", payload); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializePayload_DoesNotIncludeArgumentsWhenStatedSo_WithNewFormat_InVersion_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Sample", "[\"System.String\"]", "[\"\\\"Hello\\\"\"]"); var payload = invocationData.SerializePayload(excludeArguments: true); Assert.Equal( "{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"Sample\",\"p\":[\"System.String\"]}", payload); } [DataCompatibilityRangeFact(MinLevel = CompatibilityLevel.Version_170)] public void SerializePayload_SerializesInvocationDataToString_WithNullifyingEmptyEntries_InVersion_170() { var invocationData = new InvocationData( "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Empty", "[]", "[]"); var payload = invocationData.SerializePayload(); Assert.Equal( "{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"Empty\"}", payload); } [DataCompatibilityRangeTheory] // Previous serialization format. [InlineData("{\"$type\":\"Hangfire.Storage.InvocationData, Hangfire.Core\",\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\",\"Method\":\"Sample\",\"ParameterTypes\":\"{\\\"$type\\\":\\\"System.Type[], mscorlib\\\",\\\"$values\\\":[\\\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\\\"]}\",\"Arguments\":\"{\\\"$type\\\":\\\"System.String[], mscorlib\\\",\\\"$values\\\":[\\\"\\\\\\\"Hello\\\\\\\"\\\"]}\"}")] [InlineData("{\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\",\"Method\":\"Sample\",\"ParameterTypes\":\"{\\\"$type\\\":\\\"System.Type[], mscorlib\\\",\\\"$values\\\":[\\\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\\\"]}\",\"Arguments\":\"{\\\"$type\\\":\\\"System.String[], mscorlib\\\",\\\"$values\\\":[\\\"\\\\\\\"Hello\\\\\\\"\\\"]}\"}")] [InlineData("{\"$type\":\"Hangfire.Storage.InvocationData, Hangfire.Core\",\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\",\"Method\":\"Sample\",\"ParameterTypes\":\"[\\\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\\\"]\",\"Arguments\":\"[\\\"\\\\\\\"Hello\\\\\\\"\\\"]\"}")] [InlineData("{\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\",\"Method\":\"Sample\",\"ParameterTypes\":\"[\\\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\\\"]\",\"Arguments\":\"[\\\"\\\\\\\"Hello\\\\\\\"\\\"]\"}")] // New serialization format. [InlineData("{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"Sample\",\"p\":[\"System.String\"],\"a\":[\"\\\"Hello\\\"\"]}")] public void Deserialize_DeserializesCorrectlyStringToInvocationData(string invocationData) { try { JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); var serializedData = InvocationData.DeserializePayload(invocationData); var job = serializedData.Deserialize(); Assert.False(job.Type.GetTypeInfo().ContainsGenericParameters); Assert.Equal("Sample", job.Method.Name); Assert.Equal(typeof(string), job.Method.GetParameters()[0].ParameterType); Assert.Equal(1, job.Args.Count); Assert.Equal("Hello", job.Args[0]); } finally { JobHelper.SetSerializerSettings(null); } } [DataCompatibilityRangeFact] public void Deserialize_DeserializesCorrectlyShortFormatStringToInvocationData() { var invocationData = "{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"Empty\"}"; var serializedData = InvocationData.DeserializePayload(invocationData); var job = serializedData.Deserialize(); Assert.False(job.Type.GetTypeInfo().ContainsGenericParameters); Assert.Equal("Empty", job.Method.Name); Assert.Empty(job.Method.GetParameters()); Assert.Equal(0, job.Args.Count); } [DataCompatibilityRangeFact] public void Deserialize_HandlesGenericTypes() { var serializedData = InvocationData.Serialize( Job.FromExpression>(x => x.Method())); var job = serializedData.Deserialize(); Assert.False(job.Type.GetTypeInfo().ContainsGenericParameters); Assert.Equal(typeof(string), job.Type.GetGenericArguments()[0]); } [DataCompatibilityRangeFact] public void Deserialize_HandlesGenericMethods_WithOpenTypeParameters() { var serializedData = InvocationData.Serialize( Job.FromExpression>(x => x.Method("asd", 123))); var job = serializedData.Deserialize(); Assert.False(job.Method.ContainsGenericParameters); } [DataCompatibilityRangeFact] public void Deserialize_HandlesMethodsDefinedInInterfaces() { var serializedData = new InvocationData( typeof(IParent).AssemblyQualifiedName, "Method", JobHelper.ToJson(new Type[0]), JobHelper.ToJson(new string[0])); var job = serializedData.Deserialize(); Assert.Equal(typeof(IParent), job.Type); } [DataCompatibilityRangeFact] public void Deserialize_HandlesMethodsDefinedInParentInterfaces() { var serializedData = new InvocationData( typeof(IChild).AssemblyQualifiedName, "Method", JobHelper.ToJson(new Type[0]), JobHelper.ToJson(new string[0])); var job = serializedData.Deserialize(); Assert.Equal(typeof(IChild), job.Type); } [DataCompatibilityRangeFact] public void Deserialize_RethrowsJsonException_InsteadOfNullValue_WhenReferenceConverterChosen() { var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, "ListMethod", JobHelper.ToJson(new [] { typeof(IList) }), JobHelper.ToJson(new [] { "asdfasdf" })); var exception = Assert.Throws(() => serializedData.Deserialize()); Assert.IsType(exception.InnerException); } [DataCompatibilityRangeTheory] [MemberData(nameof(MemberData))] public void Serialize_CorrectlySerializesJobToInvocationData(Job job, string typeName, string method, string parameterTypes, string serializedArgs) { try { InvocationData.SetTypeSerializer(TypeHelper.SimpleAssemblyTypeSerializer); var invocationData = InvocationData.Serialize(job); Assert.Equal(typeName, invocationData.Type); Assert.Equal(method, invocationData.Method); Assert.Equal(parameterTypes, invocationData.ParameterTypes); Assert.Equal(serializedArgs, invocationData.Arguments); } finally { InvocationData.SetTypeSerializer(null); } } [DataCompatibilityRangeTheory] [MemberData(nameof(MemberData))] public void Deserialize_CorrectlyDeserializesJobFromInvocationData(Job job, string typeName, string method, string parameterTypes, string serializedArgs) { var deserializedJob = new InvocationData(typeName, method, parameterTypes, serializedArgs).Deserialize(); #if NETCOREAPP1_0 || NETCOREAPP2_1 Assert.Equal(job.Type.FullName, deserializedJob.Type.FullName); Assert.Equal(job.Method.Name, deserializedJob.Method.Name); #else Assert.Equal(job.Type, deserializedJob.Type); Assert.Equal(job.Method, deserializedJob.Method); #endif var parameters = job.Method.GetParameters(); var deserializedParameters = deserializedJob.Method.GetParameters(); for (var i = 0; i < parameters.Length; i++) { Assert.Equal(parameters[i].ParameterType, deserializedParameters[i].ParameterType); } for (var i = 0; i < job.Args.Count; i++) { Assert.Equal(job.Args[i], deserializedJob.Args[i]); } } public static IEnumerable MemberData { get { return new [] { new object[] { Job.FromExpression(() => Thread.Sleep(TimeSpan.FromSeconds(5))), "System.Threading.Thread, mscorlib", "Sleep", "[\"System.TimeSpan, mscorlib\"]", "[\"\\\"00:00:05\\\"\"]" }, new object[] { Job.FromExpression(() => Console.WriteLine("4567")), "System.Console, mscorlib", "WriteLine", "[\"System.String\"]", "[\"\\\"4567\\\"\"]" }, new object[] { Job.FromExpression(() => Sample("str1")), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "Sample", "[\"System.String\"]", "[\"\\\"str1\\\"\"]" }, new object[] { Job.FromExpression(() => ListMethod(new string[0])), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "ListMethod", "[\"System.Collections.Generic.IList`1[[System.String]], mscorlib\"]", "[\"[]\"]" }, new object[] { Job.FromExpression(() => GenericMethod(1)), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "GenericMethod", "[\"System.Int32\"]", "[\"1\"]" }, #if !NETCOREAPP1_0 new object[] { Job.FromExpression(() => GenericMethod((StringDictionary)null)), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "GenericMethod", "[\"System.Collections.Specialized.StringDictionary, System\"]", "[null]" }, #endif new object[] { Job.FromExpression(() => GenericMethod((InvocationDataFacts)null)), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "GenericMethod", "[\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\"]", "[null]" }, new object[] { Job.FromExpression(() => GenericMethod((GlobalType)null)), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "GenericMethod", "[\"GlobalType, Hangfire.Core.Tests\"]", "[null]" }, new object[] { Job.FromExpression(() => OtherGenericMethod(1, new List())), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "OtherGenericMethod", "[\"System.Int32\",\"System.Collections.Generic.List`1[[System.Int32]], mscorlib\"]", "[\"1\",\"[]\"]" }, new object[] { Job.FromExpression(x => x.Method()), "Hangfire.Core.Tests.Storage.InvocationDataFacts+NestedType, Hangfire.Core.Tests", "Method", "[]", "[]" }, new object[] { Job.FromExpression(x => x.NestedGenericMethod(1)), "Hangfire.Core.Tests.Storage.InvocationDataFacts+NestedType, Hangfire.Core.Tests", "NestedGenericMethod", "[\"System.Int32\"]", "[\"1\"]" }, new object[] { Job.FromExpression(() => ArrayOfNested(new NestedType[0])), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "ArrayOfNested", "[\"Hangfire.Core.Tests.Storage.InvocationDataFacts+NestedType[], Hangfire.Core.Tests\"]", "[\"[]\"]" }, new object[] { Job.FromExpression(() => ArrayOfNestedGeneric(new NestedGenericType[0])), "Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests", "ArrayOfNestedGeneric", "[\"Hangfire.Core.Tests.Storage.InvocationDataFacts+NestedGenericType`1[[System.Int32]][], Hangfire.Core.Tests\"]", "[\"[]\"]" }, new object[] { Job.FromExpression>(x => x.Method()), "Hangfire.Core.Tests.Storage.InvocationDataFacts+GenericType`1[[System.Int32]], Hangfire.Core.Tests", "Method", "[]", "[]" }, new object[] { Job.FromExpression>(x => x.Method()), "Hangfire.Core.Tests.Storage.InvocationDataFacts+GenericType`1[[GlobalType, Hangfire.Core.Tests]], Hangfire.Core.Tests", "Method", "[]", "[]" }, new object[] { Job.FromExpression>(x => x.Method()), "Hangfire.Core.Tests.Storage.InvocationDataFacts+GenericType`1[[Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests]], Hangfire.Core.Tests", "Method", "[]", "[]" }, new object[] { Job.FromExpression>(x => x.Method(1, 1)), "Hangfire.Core.Tests.Storage.InvocationDataFacts+GenericType`1[[System.Int32]], Hangfire.Core.Tests", "Method", "[\"System.Int32\",\"System.Int32\"]", "[\"1\",\"1\"]" }, new object[] { Job.FromExpression.NestedGenericType>(x => x.Method(1, "1")), "Hangfire.Core.Tests.Storage.InvocationDataFacts+GenericType`1+NestedGenericType`1[[System.Int32],[System.String]], Hangfire.Core.Tests", "Method", "[\"System.Int32\",\"System.String\"]", "[\"1\",\"\\\"1\\\"\"]" }, new object[] { Job.FromExpression(x => x.Method()), "GlobalType, Hangfire.Core.Tests", "Method", "[]", "[]" }, new object[] { Job.FromExpression(x => x.GenericMethod(1)), "GlobalType, Hangfire.Core.Tests", "GenericMethod", "[\"System.Int32\"]", "[\"1\"]" }, new object[] { Job.FromExpression(x => x.NestedMethod()), "GlobalType+NestedType, Hangfire.Core.Tests", "NestedMethod", "[]", "[]" }, new object[] { Job.FromExpression>(x => x.NestedGenericMethod(1, 1)), "GlobalType+NestedGenericType`1[[System.Int64]], Hangfire.Core.Tests", "NestedGenericMethod", "[\"System.Int64\",\"System.Int32\"]", "[\"1\",\"1\"]" }, new object[] { Job.FromExpression>(x => x.Method()), "GlobalGenericType`1[[System.Int32]], Hangfire.Core.Tests", "Method", "[]", "[]" }, new object[] { Job.FromExpression>(x => x.GenericMethod(1)), "GlobalGenericType`1[[System.Object]], Hangfire.Core.Tests", "GenericMethod", "[\"System.Int32\"]", "[\"1\"]" }, new object[] { Job.FromExpression.NestedType>(x => x.Method()), "GlobalGenericType`1+NestedType[[System.Int32]], Hangfire.Core.Tests", "Method", "[]", "[]" }, new object[] { Job.FromExpression.NestedGenericType>(x => x.Method(1, 1)), "GlobalGenericType`1+NestedGenericType`1[[System.Int64],[System.Int32]], Hangfire.Core.Tests", "Method", "[\"System.Int64\",\"System.Int32\"]", "[\"1\",\"1\"]" }, }; } } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_LocalDateTimeArguments_ConvertedToRoundtripFormat() { var value = DateTime.Now; var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(DateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime) }), JobHelper.ToJson(new[] { value.ToString("o", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); Assert.Equal(value, (DateTime)job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_UnknownDateTimeArguments_ConvertedToRoundtripFormat() { var value = new DateTime(2017, 1, 1, 1, 1, 1, 1, DateTimeKind.Unspecified); var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(DateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime) }), JobHelper.ToJson(new[] { value.ToString("o", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); Assert.Equal(value, (DateTime)job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_UtcDateTimeArguments_ConvertedToRoundtripFormat() { var value = DateTime.UtcNow; var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(DateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime) }), JobHelper.ToJson(new[] { value.ToString("o", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); Assert.Equal(value, (DateTime)job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_LocalDateTimeArguments_ConvertedToOldFormat_WithLoweredPrecision() { var value = DateTime.Now; var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(DateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime) }), JobHelper.ToJson(new[] { value.ToString("MM/dd/yyyy HH:mm:ss.ffff", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); var actualValue = (DateTime)job.Args[0]; Assert.Equal(value.Year, actualValue.Year); Assert.Equal(value.Month, actualValue.Month); Assert.Equal(value.Day, actualValue.Day); Assert.Equal(value.Hour, actualValue.Hour); Assert.Equal(value.Minute, actualValue.Minute); Assert.Equal(value.Second, actualValue.Second); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_UnknownDateTimeArguments_ConvertedToOldFormat_WithLoweredPrecision() { var value = new DateTime(2017, 1, 1, 1, 1, 1, 1, DateTimeKind.Unspecified); var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(DateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime) }), JobHelper.ToJson(new[] { value.ToString("MM/dd/yyyy HH:mm:ss.ffff", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); var actualValue = (DateTime)job.Args[0]; Assert.Equal(value.Year, actualValue.Year); Assert.Equal(value.Month, actualValue.Month); Assert.Equal(value.Day, actualValue.Day); Assert.Equal(value.Hour, actualValue.Hour); Assert.Equal(value.Minute, actualValue.Minute); Assert.Equal(value.Second, actualValue.Second); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_UtcDateTimeArguments_ConvertedToOldFormat_WithLoweredPrecision() { var value = DateTime.UtcNow; var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(DateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime) }), JobHelper.ToJson(new[] { value.ToString("MM/dd/yyyy HH:mm:ss.ffff", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); var actualValue = (DateTime)job.Args[0]; Assert.Equal(value.Year, actualValue.Year); Assert.Equal(value.Month, actualValue.Month); Assert.Equal(value.Day, actualValue.Day); Assert.Equal(value.Hour, actualValue.Hour); Assert.Equal(value.Minute, actualValue.Minute); Assert.Equal(value.Second, actualValue.Second); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_NullableUtcDateTimeArguments() { DateTime? value = DateTime.UtcNow; var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(NullableDateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime?) }), JobHelper.ToJson(new[] { value.Value.ToString("o", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); Assert.Equal(value, job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlySeserializes_NullableUtcDateTimeArguments_With_Null() { DateTime? value = null; var serializedData = InvocationData.Serialize(Job.FromExpression(() => NullableDateTimeMethod(value))); var job = serializedData.Deserialize(); Assert.Equal(value, job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_NullableLocalDateTimeArguments() { DateTime? value = DateTime.Now; var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(NullableDateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime?) }), JobHelper.ToJson(new[] { value.Value.ToString("o", CultureInfo.InvariantCulture) })); var job = serializedData.Deserialize(); Assert.Equal(value, job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_NullableDateTimeArguments_With_Null_Value() { DateTime? value = null; var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, nameof(NullableDateTimeMethod), JobHelper.ToJson(new[] { typeof(DateTime?) }), JobHelper.ToJson(new[] { value })); var job = serializedData.Deserialize(); Assert.Equal(value, job.Args[0]); } [DataCompatibilityRangeFact] public void Deserialize_CorrectlyDeserializes_DateTimesInRegularJsonFormat() { var dateTimeString = "\"2019-03-05T13:20:04.5932150Z\""; var dateTime = SerializationHelper.Deserialize(dateTimeString, SerializationOption.User); var dateTimeJob = new InvocationData( GetType().AssemblyQualifiedName, "DateTimeMethod", JobHelper.ToJson(new [] { typeof(DateTime) }), "[\"\\\"2019-03-05T13:20:04.593215Z\\\"\"]").DeserializeJob(); var nullableJob = new InvocationData( GetType().AssemblyQualifiedName, "NullableDateTimeMethod", JobHelper.ToJson(new[] { typeof(DateTime?) }), "[\"\\\"2019-03-05T13:20:04.593215Z\\\"\"]").DeserializeJob(); Assert.Equal(dateTime, dateTimeJob.Args[0]); Assert.Equal(dateTime, nullableJob.Args[0]); } [DataCompatibilityRangeFact, CleanSerializerSettings] public void Deserialize_HandlesChangingProcessOfInternalDataSerialization() { SerializationHelper.SetUserSerializerSettings(SerializerSettingsHelper.DangerousSettings); var serializedData = new InvocationData( typeof(InvocationDataFacts).AssemblyQualifiedName, "ComplicatedMethod", SerializationHelper.Serialize(new[] { typeof(IList), typeof(SomeClass) }, SerializationOption.User), SerializationHelper.Serialize(new[] { SerializationHelper.Serialize(new List { "one", "two" }, SerializationOption.User), SerializationHelper.Serialize(new SomeClass { StringValue = "value" }, SerializationOption.User) }, SerializationOption.User)); var job = serializedData.Deserialize(); Assert.Equal(typeof(InvocationDataFacts), job.Type); Assert.Equal(2, job.Args.Count); Assert.Equal(typeof(List), job.Args[0]?.GetType()); Assert.Equal("one", (job.Args[0] as List)?[0]); Assert.Equal("two", (job.Args[0] as List)?[1]); Assert.Equal(typeof(SomeClass), job.Args[1]?.GetType()); Assert.Equal("value", (job.Args[1] as SomeClass)?.StringValue); Assert.Equal(0, (job.Args[1] as SomeClass)?.DefaultValue); Assert.Null((job.Args[1] as SomeClass)?.NullObject); } #if !NET452 && !NET461 [DataCompatibilityRangeFact, CleanSerializerSettings] public void DeserializeJob_CanPreviousFormat_WhenTypeNameHandlingOptionIsSetToAll() { var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; JsonConvert.DefaultSettings = () => settings; #pragma warning disable 618 JobHelper.SetSerializerSettings(settings); #pragma warning restore 618 var job = InvocationData .DeserializePayload("{\"$type\":\"Hangfire.Storage.InvocationData, Hangfire.Core\",\"Type\":\"System.Console, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\",\"Method\":\"WriteLine\",\"ParameterTypes\":\"{\\\"$type\\\":\\\"System.Type[], mscorlib\\\",\\\"$values\\\":[\\\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\\\"]}\",\"Arguments\":\"{\\\"$type\\\":\\\"System.String[], mscorlib\\\",\\\"$values\\\":[\\\"\\\\\\\"Hello \\\\\\\"\\\"]}\"}") .DeserializeJob(); Assert.Equal("System.Console", job.Type.FullName); Assert.Equal("WriteLine", job.Method.Name); Assert.Equal("Hello ", job.Args[0]); } #endif [Fact] public void DeserializePayload_ThrowsAnException_WhenPayloadIsNull() { var exception = Assert.Throws( () => InvocationData.DeserializePayload(null)); Assert.Equal("payload", exception.ParamName); } // https://github.com/HangfireIO/Hangfire/issues/1470 [DataCompatibilityRangeFact, CleanSerializerSettings] public void DeserializePayload_CanHandleFieldBasedSerialization_OfInvocationDataClass() { #pragma warning disable 618 JobHelper.SetSerializerSettings(new JsonSerializerSettings { ContractResolver = new FieldsOnlyContractResolver() }); #pragma warning restore 618 var payload = "{\"k__BackingField\":\"System.Console, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\",\"k__BackingField\":\"WriteLine\",\"k__BackingField\":\"[]\",\"k__BackingField\":\"[]\"}"; var data = InvocationData.DeserializePayload(payload); Assert.StartsWith("System.Console", data.Type); Assert.Equal("WriteLine", data.Method); Assert.Equal("[]", data.ParameterTypes); Assert.Equal("[]", data.Arguments); } [DataCompatibilityRangeTheory] [InlineData("{\"Type\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\",\"Method\":\"Sample\",\"ParameterTypes\":\"[\\\"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\\\"]\",\"Arguments\":\"[\\\"\\\\\\\"Hello\\\\\\\"\\\"]\",\"Queue\":\"critical\"}")] [InlineData("{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"Sample\",\"p\":[\"System.String\"],\"a\":[\"\\\"Hello\\\"\"],\"q\":\"critical\"}")] public void DeserializePayload_WithQueueNameSet_ReturnsCorrectInvocationData(string payload) { var invocationData = InvocationData.DeserializePayload(payload); Assert.Contains("InvocationDataFacts", invocationData.Type); Assert.Equal("Sample", invocationData.Method); Assert.Equal("critical", invocationData.Queue); } [Fact] public void Deserialize_CorrectlyHandles_SystemXmlLinqEntities_SerializedWithNETFramework() { var job = InvocationData.DeserializePayload( "{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"XmlLinqMethod\",\"p\":[\"System.Xml.Linq.XElement, System.Xml.Linq\"],\"a\":[\"{\\\"element\\\":\\\"This is a test\\\"}\"]}") .DeserializeJob(); Assert.Equal(typeof(InvocationDataFacts), job.Type); } [Fact] public void Deserialize_CorrectlyHandles_SystemXmlLinqEntities_SerializedWithNETCore() { var job = InvocationData.DeserializePayload( "{\"t\":\"Hangfire.Core.Tests.Storage.InvocationDataFacts, Hangfire.Core.Tests\",\"m\":\"XmlLinqMethod\",\"p\":[\"System.Xml.Linq.XElement, System.Private.Xml.Linq\"],\"a\":[\"{\\\"element\\\":\\\"This is a test\\\"}\"]}") .DeserializeJob(); Assert.Equal(typeof(InvocationDataFacts), job.Type); } private class FieldsOnlyContractResolver: DefaultContractResolver { protected override List GetSerializableMembers(Type objectType) => objectType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Cast() .ToList(); protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) => base.CreateProperties(type, MemberSerialization.Fields); } [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] public static void Empty() { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Sample(string arg) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void ListMethod(IList arg) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void GenericMethod(T arg) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void OtherGenericMethod(T1 arg1, T2 arg2) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] public static void DateTimeMethod(DateTime arg) { } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] public static void NullableDateTimeMethod(DateTime? arg) { } [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void XmlLinqMethod(System.Xml.Linq.XElement value) { } [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] public static void ArrayOfNested(NestedType[] value) { } [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] public static void ArrayOfNestedGeneric(NestedGenericType[] value) { } [UsedImplicitly] public class GenericType { public void Method() { } public void Method(T1 arg1, T2 arg2) { } public class NestedGenericType { public void Method(T1 arg1, T2 arg2) { } } } [UsedImplicitly] [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] public static void ComplicatedMethod(IList arg, SomeClass objArg) { } public class SomeClass { public string StringValue { get; set; } public object NullObject { get; set; } public int DefaultValue { get; set; } } [UsedImplicitly] public class NestedType { [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method() { } [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public void NestedGenericMethod(T arg1) { } } [UsedImplicitly] public class NestedGenericType { [UsedImplicitly] public T Value { get; set; } } public interface IParent { void Method(); } public interface IChild : IParent { } } } [UsedImplicitly] [SuppressMessage("Design", "CA1050:Declare types in namespaces")] public class GlobalType { [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void Method() {} [SuppressMessage("Performance", "CA1822:Mark members as static")] [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public void GenericMethod(T arg) {} [UsedImplicitly] public class NestedType { [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] [SuppressMessage("Performance", "CA1822:Mark members as static")] public void NestedMethod() { } } [UsedImplicitly] public class NestedGenericType { public void NestedGenericMethod(T arg1, T1 arg2) { } } } [UsedImplicitly] [SuppressMessage("Design", "CA1050:Declare types in namespaces")] public class GlobalGenericType { public void Method() { } public void GenericMethod(T1 arg) { } [UsedImplicitly] public class NestedType { [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] public void Method() { } } [UsedImplicitly] public class NestedGenericType { public void Method(T arg1, T1 arg2) { } } } ================================================ FILE: tests/Hangfire.Core.Tests/Storage/MonitoringTypeFacts.cs ================================================ using System.Collections.Generic; using Hangfire.Storage.Monitoring; using Xunit; namespace Hangfire.Core.Tests.Storage { public class MonitoringTypeFacts { [Fact] public void EnqueuedJobDto_Ctor_SetsInEnqueuedState() { Assert.True(new EnqueuedJobDto().InEnqueuedState); } [Fact] public void FailedJobDto_Ctor_SetsInFailedState() { Assert.True(new FailedJobDto().InFailedState); } [Fact] public void ProcessingJobDto_Ctor_SetsInProcessingState() { Assert.True(new ProcessingJobDto().InProcessingState); } [Fact] public void ScheduledJobDto_Ctor_SetsInScheduledState() { Assert.True(new ScheduledJobDto().InScheduledState); } [Fact] public void SucceededJobDto_Ctor_SetsInSucceededState() { Assert.True(new SucceededJobDto().InSucceededState); } [Fact] public void DeletedJobDto_Ctor_SetsInDeletedState() { Assert.True(new DeletedJobDto().InDeletedState); } [Fact] public void JobList_Ctor_ShouldInitializeCollection() { var list = new JobList(new Dictionary { { "1", 2 } }); Assert.Single(list); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Storage/StorageConnectionExtensionsFacts.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Hangfire.Storage; using Moq; using Xunit; namespace Hangfire.Core.Tests.Storage { public class StorageConnectionExtensionsFacts { private readonly Mock _connection; public StorageConnectionExtensionsFacts() { _connection = new Mock(); } [Fact] public void GivenRecurringJobIsCancelledWhenGetRecurringJobsThenNotGetStateData() { _connection.Setup(o => o.GetAllItemsFromSet(It.IsAny())).Returns(new HashSet { "1" }); _connection.Setup(o => o.GetAllEntriesFromHash("recurring-job:1")).Returns(new Dictionary { { "Cron", "A"}, { "Job", @"{""Type"":""ConsoleApplication1.CommandHandler, ConsoleApplication1"",""Method"":""Handle"",""ParameterTypes"":""[\""string\""]"",""Arguments"":""[\""Text\""]""}"}, { "LastJobId", string.Empty} }).Verifiable(); var result = _connection.Object.GetRecurringJobs().Single(); Assert.Null(result.LastJobState); _connection.VerifyAll(); } [Fact] public void AcquireDistributedJobLock_AcquiresALock_WithTheCorrectResource() { var timeout = TimeSpan.FromSeconds(5); _connection.Object.AcquireDistributedJobLock("some-id", timeout); _connection.Verify(x => x.AcquireDistributedLock( "job:some-id:state-lock", timeout)); } [Fact] public void GetRecurringJobs_WithGivenIdentifiers_QueriesForCorrespondingJobs() { // Act var result = _connection.Object.GetRecurringJobs(new[] { "a", "b" }); // Assert Assert.Equal(2, result.Count); Assert.True(result[0].Removed); Assert.True(result[1].Removed); _connection.Verify(x => x.GetAllEntriesFromHash("recurring-job:a"), Times.Once); _connection.Verify(x => x.GetAllEntriesFromHash("recurring-job:b"), Times.Once); } [Fact] public void GetRecurringJobsWithNullDateTimeHashValues() { _connection.Setup(o => o.GetAllItemsFromSet(It.IsAny())).Returns(new HashSet { "1" }); _connection.Setup(o => o.GetAllEntriesFromHash("recurring-job:1")).Returns(new Dictionary { { "Cron", "A"}, { "Job", @"{""Type"":""ConsoleApplication1.CommandHandler, ConsoleApplication1"",""Method"":""Handle"",""ParameterTypes"":""[\""string\""]"",""Arguments"":""[\""Text\""]""}"}, { "NextExecution", null }, { "LastExecution", null }, { "CreatedAt", null } }).Verifiable(); var result = _connection.Object.GetRecurringJobs(); Assert.Null(result[0].LastExecution); Assert.Null(result[0].NextExecution); Assert.Null(result[0].CreatedAt); _connection.VerifyAll(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Stubs/DashboardContextStub.cs ================================================ using Hangfire.Dashboard; namespace Hangfire.Core.Tests.Stubs { class DashboardContextStub : DashboardContext { public DashboardContextStub(DashboardOptions options) : base(new JobStorageStub(), options) { Response = new DashboardResponseStub(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Stubs/DashboardResponseStub.cs ================================================ using System; using System.IO; using System.Threading.Tasks; using Hangfire.Dashboard; namespace Hangfire.Core.Tests.Stubs { class DashboardResponseStub : DashboardResponse { public override string ContentType { get; set; } public override int StatusCode { get; set; } public override Stream Body { get; } public override void SetExpire(DateTimeOffset? value) { throw new NotImplementedException(); } public override Task WriteAsync(string text) { throw new NotImplementedException(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Stubs/JobStorageStub.cs ================================================ using System; using Hangfire.Storage; namespace Hangfire.Core.Tests.Stubs { class JobStorageStub : JobStorage { public override IMonitoringApi GetMonitoringApi() { throw new NotImplementedException(); } public override IStorageConnection GetConnection() { throw new NotImplementedException(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/CleanSerializerSettingsAttribute.cs ================================================ using System.Reflection; using Hangfire.Common; using Newtonsoft.Json; using Xunit.Sdk; namespace Hangfire.Core.Tests { internal sealed class CleanSerializerSettingsAttribute : BeforeAfterTestAttribute { public override void Before(MethodInfo methodUnderTest) { ClearSettings(); } public override void After(MethodInfo methodUnderTest) { ClearSettings(); } private static void ClearSettings() { #pragma warning disable 618 JobHelper.SetSerializerSettings(null); #pragma warning restore 618 SerializationHelper.SetUserSerializerSettings(null); #if !NET452 && !NET461 JsonConvert.DefaultSettings = null; #endif } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/CultureHelper.cs ================================================ using System.Globalization; using System.Threading; namespace Hangfire.Core.Tests { internal static class CultureHelper { public static void SetCurrentCulture(CultureInfo cultureInfo) { #if !NETCOREAPP1_0 Thread.CurrentThread.CurrentCulture = cultureInfo; #else CultureInfo.CurrentCulture = cultureInfo; #endif } public static void SetCurrentUICulture(CultureInfo cultureInfo) { #if !NETCOREAPP1_0 Thread.CurrentThread.CurrentUICulture = cultureInfo; #else CultureInfo.CurrentUICulture = cultureInfo; #endif } public static void SetCurrentCulture(string id) { #if !NETCOREAPP1_0 Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(id); #else CultureInfo.CurrentCulture = new CultureInfo(id); #endif } public static void SetCurrentUICulture(string id) { #if !NETCOREAPP1_0 Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(id); #else CultureInfo.CurrentUICulture = new CultureInfo(id); #endif } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeFactAttribute.cs ================================================ using System; using System.Linq; using Xunit; using Xunit.Sdk; namespace Hangfire.Core.Tests { [XunitTestCaseDiscoverer("Hangfire.Core.Tests.DataCompatibilityRangeFactDiscoverer", "Hangfire.Core.Tests")] [AttributeUsage(AttributeTargets.Method)] internal sealed class DataCompatibilityRangeFactAttribute : FactAttribute { internal static readonly CompatibilityLevel PossibleMinLevel; internal static readonly CompatibilityLevel PossibleMaxExcludingLevel; static DataCompatibilityRangeFactAttribute() { var compatibilityLevels = Enum.GetValues(typeof(CompatibilityLevel)) .Cast() .ToArray(); PossibleMinLevel = compatibilityLevels.Min(); PossibleMaxExcludingLevel = compatibilityLevels.Max() + 1; } public DataCompatibilityRangeFactAttribute() { MinLevel = PossibleMinLevel; MaxExcludingLevel = PossibleMaxExcludingLevel; } public CompatibilityLevel MinLevel { get; set; } public CompatibilityLevel MaxExcludingLevel { get; set; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeFactDiscoverer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Hangfire.Annotations; using Xunit.Abstractions; using Xunit.Sdk; namespace Hangfire.Core.Tests { [UsedImplicitly] internal sealed class DataCompatibilityRangeFactDiscoverer : IXunitTestCaseDiscoverer { public IMessageSink DiagnosticMessageSink { get; } public DataCompatibilityRangeFactDiscoverer(IMessageSink diagnosticMessageSink) { DiagnosticMessageSink = diagnosticMessageSink; } public IEnumerable Discover( ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) { if (testMethod.Method.GetParameters().Any()) { yield return new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, "[CompatibilityLevelFact] methods are not allowed to have parameters. Did you mean to use [CompatibilityLevelTheory]?"); } else if (testMethod.Method.IsGenericMethodDefinition) { yield return new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, "[CompatibilityLevelFact] methods are not allowed to be generic."); } else { var compatibilityLevels = GetAllowedCompatibilityLevels(factAttribute); foreach (var compatibilityLevel in compatibilityLevels) { yield return new DataCompatibilityRangeTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, new object[] { compatibilityLevel }); } } } internal static CompatibilityLevel[] GetAllowedCompatibilityLevels(IAttributeInfo attributeInfo) { var compatibilityLevels = Enum.GetValues(typeof(CompatibilityLevel)) .Cast() .ToArray(); var minLevel = attributeInfo.GetNamedArgument("MinLevel"); var maxExcludingLevel = attributeInfo.GetNamedArgument("MaxExcludingLevel"); var result = new List(); foreach (var compatibilityLevel in compatibilityLevels) { if (compatibilityLevel >= minLevel && compatibilityLevel < maxExcludingLevel) { result.Add(compatibilityLevel); } } return result.ToArray(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeTestCase.cs ================================================ using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; using Xunit.Sdk; namespace Hangfire.Core.Tests { internal sealed class DataCompatibilityRangeTestCase : XunitTestCase { #pragma warning disable 618 public DataCompatibilityRangeTestCase() #pragma warning restore 618 { } public DataCompatibilityRangeTestCase( IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments) : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) { } public override Task RunAsync( IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { return new DataCompatibilityRangeTestCaseRunner( this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource) .RunAsync(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeTestCaseRunner.cs ================================================ using System.Threading; using System.Threading.Tasks; using Xunit.Sdk; namespace Hangfire.Core.Tests { internal sealed class DataCompatibilityRangeTestCaseRunner : XunitTestCaseRunner { public DataCompatibilityRangeTestCaseRunner( IXunitTestCase testCase, string displayName, string skipReason, object[] constructorArguments, object[] testMethodArguments, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource) { } protected override Task RunTestAsync() { return new DataCompatibilityRangeTestRunner(new XunitTest(TestCase, DisplayName), MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, SkipReason, BeforeAfterAttributes, new ExceptionAggregator(Aggregator), CancellationTokenSource) .RunAsync(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeTestRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; using Xunit.Sdk; namespace Hangfire.Core.Tests { internal sealed class DataCompatibilityRangeTestRunner : XunitTestRunner { private static readonly SemaphoreSlim SyncRoot = new SemaphoreSlim(1, 1); public DataCompatibilityRangeTestRunner( ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource) { } protected override async Task> InvokeTestAsync(ExceptionAggregator aggregator) { CompatibilityLevel? oldCompatibilityLevel = null; try { await SyncRoot.WaitAsync(CancellationTokenSource.Token); var compatibilityLevel = (CompatibilityLevel)TestMethodArguments[TestMethodArguments.Length - 1]; TestMethodArguments = TestMethodArguments.Take(TestMethodArguments.Length - 1).ToArray(); oldCompatibilityLevel = GlobalConfiguration.CompatibilityLevel; GlobalConfiguration.CompatibilityLevel = compatibilityLevel; return await base.InvokeTestAsync(aggregator); } finally { if (oldCompatibilityLevel.HasValue) GlobalConfiguration.CompatibilityLevel = oldCompatibilityLevel.Value; SyncRoot.Release(); } } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeTheoryAttribute.cs ================================================ using System; using Xunit; using Xunit.Sdk; namespace Hangfire.Core.Tests { [XunitTestCaseDiscoverer("Hangfire.Core.Tests.DataCompatibilityRangeTheoryDiscoverer", "Hangfire.Core.Tests")] [AttributeUsage(AttributeTargets.Method)] internal sealed class DataCompatibilityRangeTheoryAttribute : TheoryAttribute { public DataCompatibilityRangeTheoryAttribute() { MinLevel = DataCompatibilityRangeFactAttribute.PossibleMinLevel; MaxExcludingLevel = DataCompatibilityRangeFactAttribute.PossibleMaxExcludingLevel; } public CompatibilityLevel MinLevel { get; set; } public CompatibilityLevel MaxExcludingLevel { get; set; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeTheoryDiscoverer.cs ================================================ using System; using System.Collections.Generic; using System.Linq; #if NETCOREAPP1_0 using System.Reflection; #endif using Hangfire.Annotations; using Xunit.Abstractions; using Xunit.Sdk; namespace Hangfire.Core.Tests { [UsedImplicitly] internal class DataCompatibilityRangeTheoryDiscoverer : IXunitTestCaseDiscoverer { public DataCompatibilityRangeTheoryDiscoverer(IMessageSink diagnosticMessageSink) { DiagnosticMessageSink = diagnosticMessageSink; } protected IMessageSink DiagnosticMessageSink { get; } protected virtual IEnumerable CreateTestCasesForTheory( ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) { var compatibilityLevels = DataCompatibilityRangeFactDiscoverer.GetAllowedCompatibilityLevels(theoryAttribute); foreach (var compatibilityLevel in compatibilityLevels) { yield return new DataCompatibilityRangeTheoryTestCase( compatibilityLevel, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod); } } protected virtual IEnumerable CreateTestCasesForDataRow( ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) { var compatibilityLevels = DataCompatibilityRangeFactDiscoverer.GetAllowedCompatibilityLevels(theoryAttribute); foreach (var compatibilityLevel in compatibilityLevels) { yield return new DataCompatibilityRangeTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow.Concat(new object[] { compatibilityLevel }).ToArray()); } } protected virtual IEnumerable CreateTestCasesForSkip( ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, string skipReason) { return new[] { new XunitTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) }; } protected virtual IEnumerable CreateTestCasesForSkippedDataRow( ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason) { return new[] { new XunitSkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, skipReason, dataRow) }; } public virtual IEnumerable Discover( ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) { var skipArgument = theoryAttribute.GetNamedArgument("Skip"); if (skipArgument != null) { return CreateTestCasesForSkip(discoveryOptions, testMethod, theoryAttribute, skipArgument); } if (discoveryOptions.PreEnumerateTheoriesOrDefault()) { try { var customAttributes = testMethod.Method.GetCustomAttributes(typeof(DataAttribute)); var testCases = new List(); foreach (var attributeInfo in customAttributes) { var dataDiscovererAttribute = attributeInfo.GetCustomAttributes(typeof(DataDiscovererAttribute)).First(); IDataDiscoverer dataDiscoverer; try { dataDiscoverer = ExtensibilityPointFactory.GetDataDiscoverer(DiagnosticMessageSink, dataDiscovererAttribute); } catch (InvalidCastException) { if (attributeInfo is IReflectionAttributeInfo reflectionAttributeInfo) { testCases.Add(new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, $"Data discoverer specified for {reflectionAttributeInfo.Attribute.GetType()} on {testMethod.TestClass.Class.Name}.{testMethod.Method.Name} does not implement IDataDiscoverer.")); continue; } testCases.Add(new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, $"A data discoverer specified on {testMethod.TestClass.Class.Name}.{testMethod.Method.Name} does not implement IDataDiscoverer.")); continue; } if (dataDiscoverer == null) { if (attributeInfo is IReflectionAttributeInfo reflectionAttributeInfo) { testCases.Add(new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, $"Data discoverer specified for {reflectionAttributeInfo.Attribute.GetType()} on {testMethod.TestClass.Class.Name}.{testMethod.Method.Name} does not exist.")); } else { testCases.Add(new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, $"A data discoverer specified on {testMethod.TestClass.Class.Name}.{testMethod.Method.Name} does not exist.")); } } else { if (!dataDiscoverer.SupportsDiscoveryEnumeration(attributeInfo, testMethod.Method)) { testCases.Add(new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, $"DataDiscoverer doesn't support discovery enumeration for {testMethod.TestClass.Class.Name}.{testMethod.Method.Name}.")); } var data = dataDiscoverer.GetData(attributeInfo, testMethod.Method); if (data == null) { testCases.Add(new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, $"Test data returned null for {testMethod.TestClass.Class.Name}.{testMethod.Method.Name}. Make sure it is statically initialized before this test method is called.")); } else { var serializationHelperType = Type.GetType("Xunit.Sdk.SerializationHelper, xunit.execution.desktop", throwOnError: false); if (serializationHelperType == null) { serializationHelperType = Type.GetType("Xunit.Sdk.SerializationHelper, xunit.execution.dotnet", throwOnError: false); if (serializationHelperType == null) { DiagnosticMessageSink.OnMessage(new DiagnosticMessage( $"Xunit.Sdk.SerializationHelper type not found for {testMethod.TestClass.Class.Name}.{testMethod.Method.Name}; falling back to single test case.")); return CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); } } var isSerializableMethod = serializationHelperType.GetMethod("IsSerializable", new [] { typeof(object) }); if (isSerializableMethod == null) { DiagnosticMessageSink.OnMessage(new DiagnosticMessage( $"Xunit.Sdk.SerializationHelper.IsSerializable method not found for {testMethod.TestClass.Class.Name}.{testMethod.Method.Name}; falling back to single test case.")); return CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); } var skipArgument2 = attributeInfo.GetNamedArgument("Skip"); foreach (var dataRow in data) { if (!(bool)isSerializableMethod.Invoke(null, new object[] { dataRow })) { DiagnosticMessageSink.OnMessage(new DiagnosticMessage( $"Non-serializable data ('{dataRow.GetType().FullName}') found for '{testMethod.TestClass.Class.Name}.{testMethod.Method.Name}'; falling back to single test case.")); return CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); } var collection = skipArgument2 != null ? CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipArgument2) : CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow); testCases.AddRange(collection); } } } } if (testCases.Count == 0) { testCases.Add(new ExecutionErrorTestCase( DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, $"No data found for {testMethod.TestClass.Class.Name}.{testMethod.Method.Name}")); } return testCases; } catch (Exception ex) { DiagnosticMessageSink.OnMessage(new DiagnosticMessage( $"Exception thrown during theory discovery on '{testMethod.TestClass.Class.Name}.{testMethod.Method.Name}'; falling back to single test case.{Environment.NewLine}{ex}")); } } return CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeTheoryTestCase.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Hangfire.Annotations; using Xunit.Abstractions; using Xunit.Sdk; namespace Hangfire.Core.Tests { internal sealed class DataCompatibilityRangeTheoryTestCase : XunitTestCase { [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] [UsedImplicitly] public DataCompatibilityRangeTheoryTestCase() { } public DataCompatibilityRangeTheoryTestCase( CompatibilityLevel compatibilityLevel, IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod) : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, new object[] { compatibilityLevel }) { } public override Task RunAsync( IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { return new DataCompatibilityRangeTheoryTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource).RunAsync(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/DataCompatibilityRangeTheoryTestCaseRunner.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; using Xunit.Sdk; namespace Hangfire.Core.Tests { public class DataCompatibilityRangeTheoryTestCaseRunner : XunitTestCaseRunner { private readonly ExceptionAggregator _cleanupAggregator = new ExceptionAggregator(); private Exception _dataDiscoveryException; private readonly IMessageSink _diagnosticMessageSink; private readonly List _testRunners = new List(); private readonly List _toDispose = new List(); public DataCompatibilityRangeTheoryTestCaseRunner( IXunitTestCase testCase, string displayName, string skipReason, object[] constructorArguments, object[] testMethodArguments, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource) { _diagnosticMessageSink = diagnosticMessageSink; } protected override async Task AfterTestCaseStartingAsync() { await base.AfterTestCaseStartingAsync(); try { var dataAttributes = TestCase.TestMethod.Method.GetCustomAttributes(typeof(DataAttribute)); foreach (var dataAttribute in dataAttributes) { var discovererAttribute = dataAttribute.GetCustomAttributes(typeof(DataDiscovererAttribute)).First(); var args = discovererAttribute.GetConstructorArguments().Cast().ToList(); var discovererType = Type.GetType($"{args[0]}, {args[1]}"); if (discovererType == null) { if (dataAttribute is IReflectionAttributeInfo reflectionAttribute) Aggregator.Add(new InvalidOperationException($"Data discoverer specified for {reflectionAttribute.Attribute.GetType()} on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not exist.")); else Aggregator.Add(new InvalidOperationException($"A data discoverer specified on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not exist.")); continue; } IDataDiscoverer discoverer; try { discoverer = ExtensibilityPointFactory.GetDataDiscoverer(_diagnosticMessageSink, discovererType); } catch (InvalidCastException) { Aggregator.Add(dataAttribute is IReflectionAttributeInfo reflectionAttribute ? new InvalidOperationException( $"Data discoverer specified for {reflectionAttribute.Attribute.GetType()} on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not implement IDataDiscoverer.") : new InvalidOperationException( $"A data discoverer specified on {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name} does not implement IDataDiscoverer.")); continue; } var data = discoverer.GetData(dataAttribute, TestCase.TestMethod.Method); if (data == null) { Aggregator.Add(new InvalidOperationException($"Test data returned null for {TestCase.TestMethod.TestClass.Class.Name}.{TestCase.TestMethod.Method.Name}. Make sure it is statically initialized before this test method is called.")); continue; } foreach (var dataRow in data) { _toDispose.AddRange(dataRow.OfType()); ITypeInfo[] resolvedTypes = null; var methodToRun = TestMethod; var convertedDataRow = methodToRun.ResolveMethodArguments(dataRow); if (methodToRun.IsGenericMethodDefinition) { resolvedTypes = TestCase.TestMethod.Method.ResolveGenericTypes(convertedDataRow); methodToRun = methodToRun.MakeGenericMethod(resolvedTypes.Select(t => ((IReflectionTypeInfo)t).Type).ToArray()); } var parameterTypes = methodToRun.GetParameters().Select(p => p.ParameterType).ToArray(); convertedDataRow = Reflector.ConvertArguments(convertedDataRow, parameterTypes); object compatibilityLevel; if (TestMethodArguments[0] is CompatibilityLevel level) { compatibilityLevel = level; } else { compatibilityLevel = Enum.Parse(typeof(CompatibilityLevel), (string)TestMethodArguments[0]); } var finalDataRow = convertedDataRow.Concat(new [] { compatibilityLevel }).ToArray(); var theoryDisplayName = TestCase.TestMethod.Method.GetDisplayNameWithArguments(DisplayName, finalDataRow, resolvedTypes); var test = new XunitTest(TestCase, theoryDisplayName); var skipReason = SkipReason ?? dataAttribute.GetNamedArgument("Skip"); var testRunner = new DataCompatibilityRangeTestRunner(test, MessageBus, TestClass, ConstructorArguments, methodToRun, finalDataRow, skipReason, BeforeAfterAttributes, Aggregator, CancellationTokenSource); _testRunners.Add(testRunner); } } } catch (Exception ex) { _dataDiscoveryException = ex; } } protected override Task BeforeTestCaseFinishedAsync() { Aggregator.Aggregate(_cleanupAggregator); return base.BeforeTestCaseFinishedAsync(); } protected override async Task RunTestAsync() { if (_dataDiscoveryException != null) return RunTest_DataDiscoveryException(); var runSummary = new RunSummary(); foreach (var testRunner in _testRunners) runSummary.Aggregate(await testRunner.RunAsync()); var timer = new ExecutionTimer(); foreach (var disposable in _toDispose) timer.Aggregate(() => _cleanupAggregator.Run(disposable.Dispose)); runSummary.Time += timer.Total; return runSummary; } private RunSummary RunTest_DataDiscoveryException() { var test = new XunitTest(TestCase, DisplayName); if (!MessageBus.QueueMessage(new TestStarting(test))) CancellationTokenSource.Cancel(); else if (!MessageBus.QueueMessage(new TestFailed(test, 0, null, Unwrap(_dataDiscoveryException)))) CancellationTokenSource.Cancel(); if (!MessageBus.QueueMessage(new TestFinished(test, 0, null))) CancellationTokenSource.Cancel(); return new RunSummary { Total = 1, Failed = 1 }; } private static Exception Unwrap(Exception ex) { while (true) { if (ex is TargetInvocationException invocationException) ex = invocationException.InnerException; else break; } return ex; } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/GlobalLockAttribute.cs ================================================ using System.Reflection; using System.Threading; using Xunit.Sdk; namespace Hangfire.Core.Tests { internal sealed class GlobalLockAttribute : BeforeAfterTestAttribute { private readonly object _globalLock = new object(); public string Reason { get; set; } public override void Before(MethodInfo methodUnderTest) { Monitor.Enter(_globalLock); } public override void After(MethodInfo methodUnderTest) { Monitor.Exit(_globalLock); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/PlatformHelper.cs ================================================ using System; #if NETCOREAPP1_0 using System.Runtime.InteropServices; #endif namespace Hangfire.Core.Tests { internal static class PlatformHelper { public static bool IsRunningOnWindows() { #if !NETCOREAPP1_0 return Environment.OSVersion.Platform == PlatformID.Win32NT; #else return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #endif } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/SequenceAttribute.cs ================================================ using System; using System.Reflection; using Moq.Sequences; using Xunit; using Xunit.Sdk; namespace Hangfire.Core.Tests { public class SequenceAttribute : BeforeAfterTestAttribute { private IDisposable _sequence; public override void Before(MethodInfo methodUnderTest) { _sequence = Sequence.Create(); } public override void After(MethodInfo methodUnderTest) { _sequence.Dispose(); } } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/SerializerSettingsHelper.cs ================================================ using System.Runtime.Serialization.Formatters; using Newtonsoft.Json; namespace Hangfire.Core.Tests { public static class SerializerSettingsHelper { public static JsonSerializerSettings DangerousSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, #if NET452 || NET461 || NETCOREAPP3_1 TypeNameAssemblyFormat = FormatterAssemblyStyle.Full, #else TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full, #endif DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, }; } } ================================================ FILE: tests/Hangfire.Core.Tests/Utils/StaticLockAttribute.cs ================================================ using System; using System.Collections.Concurrent; using System.Reflection; using System.Threading; using Xunit.Sdk; namespace Hangfire.Core.Tests { internal sealed class StaticLockAttribute : BeforeAfterTestAttribute { private readonly ConcurrentDictionary _locks = new ConcurrentDictionary(); public override void Before(MethodInfo methodUnderTest) { var type = GetType(methodUnderTest); _locks.TryAdd(type, new object()); Monitor.Enter(_locks[type]); } public override void After(MethodInfo methodUnderTest) { Monitor.Exit(_locks[GetType(methodUnderTest)]); } private static Type GetType(MethodInfo methodInfo) { return methodInfo.DeclaringType; } } } ================================================ FILE: tests/Hangfire.Core.Tests/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.2": { "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net452": "1.0.3" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Moq.Sequences": { "type": "Direct", "requested": "[2.1.0, )", "resolved": "2.1.0", "contentHash": "0mD4H/K+WtwcICaI8ytRnozVOkSGyEdQaCVcBKo3WUsAAJXfhKhpraiZZB2rMl/9jB9S6uo3x4GMbqaKTkwvgA==", "dependencies": { "Moq": "4.7.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[5.0.1, )", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==" }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==" }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.NETFramework.ReferenceAssemblies.net452": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "kuFOgilYbs29xENHlqQ6aJYa+t56u+OqHx85P7GYLVlo7HL3nsug9IQY2DoPgkOpZ2xb9btYV2EFK7Enll8S3A==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==" }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } } }, ".NETFramework,Version=v4.6.1": { "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Moq.Sequences": { "type": "Direct", "requested": "[2.1.0, )", "resolved": "2.1.0", "contentHash": "0mD4H/K+WtwcICaI8ytRnozVOkSGyEdQaCVcBKo3WUsAAJXfhKhpraiZZB2rMl/9jB9S6uo3x4GMbqaKTkwvgA==", "dependencies": { "Moq": "4.7.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[5.0.1, )", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==" }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==" }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.NETFramework.ReferenceAssemblies.net461": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==" }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } } }, "net6.0": { "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==", "dependencies": { "Microsoft.CodeCoverage": "17.8.0", "Microsoft.TestPlatform.TestHost": "17.8.0" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "NETStandard.Library": "1.6.1", "System.Linq.Queryable": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Moq.Sequences": { "type": "Direct", "requested": "[2.1.0, )", "resolved": "2.1.0", "contentHash": "0mD4H/K+WtwcICaI8ytRnozVOkSGyEdQaCVcBKo3WUsAAJXfhKhpraiZZB2rMl/9jB9S6uo3x4GMbqaKTkwvgA==", "dependencies": { "Moq": "4.7.0", "NETStandard.Library": "1.6.1" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.3, )", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==", "dependencies": { "Microsoft.NET.Test.Sdk": "15.0.0" } }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==", "dependencies": { "NETStandard.Library": "1.6.1", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.TypeConverter": "4.3.0", "System.Diagnostics.TraceSource": "4.3.0", "System.Dynamic.Runtime": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Xml.XmlDocument": "4.3.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "AYy6vlpGMfz5kOFq99L93RGbqftW/8eQTqjT9iGXW6s9MRP3UdtY8idJ8rJcjeSja8A18IhIro5YnH3uv1nz4g==", "dependencies": { "NuGet.Frameworks": "6.5.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "9ivcl/7SGRmOT0YYrHQGohWiT5YCpkmy/UEzldfVisLm6QxbLaK3FAJqZXI34rnRLmqqDCeMQxKINwmKwAPiDw==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "17.8.0", "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "NETStandard.Library": { "type": "Transitive", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "6.5.0", "contentHash": "QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg==" }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", "dependencies": { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Collections.NonGeneric": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections.Specialized": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", "dependencies": { "System.Collections.NonGeneric": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.ComponentModel.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", "dependencies": { "System.ComponentModel": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.ComponentModel.TypeConverter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", "dependencies": { "System.Collections": "4.3.0", "System.Collections.NonGeneric": "4.3.0", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.Primitives": "4.3.0", "System.Globalization": "4.3.0", "System.Linq": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.TraceSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Linq": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Emit.Lightweight": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Linq.Queryable": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "In1Bmmvl/j52yPu3xgakQSI0YIckPUr870w4K5+Lak3JCCa8hl+my65lABOuKfYs4ugmZy25ScFerC4nz8+b6g==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", "dependencies": { "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.6.0", "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.Apple": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", "dependencies": { "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Cng": "4.3.0", "System.Security.Cryptography.Csp": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } } }, "net8.0": { "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==", "dependencies": { "Microsoft.CodeCoverage": "17.8.0", "Microsoft.TestPlatform.TestHost": "17.8.0" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "NETStandard.Library": "1.6.1", "System.Linq.Queryable": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Moq.Sequences": { "type": "Direct", "requested": "[2.1.0, )", "resolved": "2.1.0", "contentHash": "0mD4H/K+WtwcICaI8ytRnozVOkSGyEdQaCVcBKo3WUsAAJXfhKhpraiZZB2rMl/9jB9S6uo3x4GMbqaKTkwvgA==", "dependencies": { "Moq": "4.7.0", "NETStandard.Library": "1.6.1" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.3, )", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==", "dependencies": { "Microsoft.NET.Test.Sdk": "15.0.0" } }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==", "dependencies": { "NETStandard.Library": "1.6.1", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.TypeConverter": "4.3.0", "System.Diagnostics.TraceSource": "4.3.0", "System.Dynamic.Runtime": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Xml.XmlDocument": "4.3.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "AYy6vlpGMfz5kOFq99L93RGbqftW/8eQTqjT9iGXW6s9MRP3UdtY8idJ8rJcjeSja8A18IhIro5YnH3uv1nz4g==", "dependencies": { "NuGet.Frameworks": "6.5.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "9ivcl/7SGRmOT0YYrHQGohWiT5YCpkmy/UEzldfVisLm6QxbLaK3FAJqZXI34rnRLmqqDCeMQxKINwmKwAPiDw==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "17.8.0", "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "NETStandard.Library": { "type": "Transitive", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "6.5.0", "contentHash": "QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg==" }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", "dependencies": { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Collections.NonGeneric": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections.Specialized": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", "dependencies": { "System.Collections.NonGeneric": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.ComponentModel.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", "dependencies": { "System.ComponentModel": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.ComponentModel.TypeConverter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", "dependencies": { "System.Collections": "4.3.0", "System.Collections.NonGeneric": "4.3.0", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.Primitives": "4.3.0", "System.Globalization": "4.3.0", "System.Linq": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.TraceSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Linq": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Emit.Lightweight": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Linq.Queryable": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "In1Bmmvl/j52yPu3xgakQSI0YIckPUr870w4K5+Lak3JCCa8hl+my65lABOuKfYs4ugmZy25ScFerC4nz8+b6g==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", "dependencies": { "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.6.0", "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.Apple": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", "dependencies": { "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Cng": "4.3.0", "System.Security.Cryptography.Csp": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } } } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/Hangfire.SqlServer.Msmq.Tests.csproj ================================================  net452 0618 ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/MessageQueueExtensionsFacts.cs ================================================ using MQTools; using Xunit; namespace Hangfire.SqlServer.Msmq.Tests { public class MessageQueueExtensionsFacts { [Theory] [InlineData(@"ComputerName\Private$\QueueName", "ComputerName", "Private$", "QueueName")] [InlineData(@"ComputerName\QueueName", "ComputerName", "", "QueueName")] [InlineData(@".\Private$\QueueName", ".", "Private$", "QueueName")] [InlineData(@".\QueueName", ".", "", "QueueName")] [InlineData(@"FormatName:Direct=OS:ComputerName\QueueName", "ComputerName", "", "QueueName")] [InlineData(@"FormatName:Direct=OS:ComputerName\Private$\QueueName", "ComputerName", "Private$", "QueueName")] public void QueueRegex_CorrectlyParsesPublicAndPrivateQueuePaths( string queuePath, string expectedComputerName, string expectedQueueType, string expectedQueueName) { var match = MessageQueueExtensions.GetQueuePathMatch(queuePath); Assert.NotNull(match); Assert.Equal(expectedComputerName, match.Groups["computerName"].Value); Assert.Equal(expectedQueueType, match.Groups["queueType"].Value); Assert.Equal(expectedQueueName, match.Groups["queue"].Value); } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/MsmqJobQueueFacts.cs ================================================ using System; using System.Data; using System.Messaging; using System.Threading; using Moq; using Xunit; // ReSharper disable PossibleNullReferenceException namespace Hangfire.SqlServer.Msmq.Tests { public class MsmqJobQueueFacts { private readonly CancellationToken _token; private readonly Mock _connection; public MsmqJobQueueFacts() { _token = new CancellationToken(); _connection = new Mock(); } [Fact] public void Ctor_ThrowsAnException_WhenPathPatternIsNull() { var exception = Assert.Throws( () => new MsmqJobQueue(null, MsmqTransactionType.Internal)); Assert.Equal("pathPattern", exception.ParamName); } [Fact, CleanMsmqQueue("my-queue")] public void Enqueue_SendsTheJobId() { // Arrange var queue = CreateQueue(MsmqTransactionType.Internal); // Act queue.Enqueue(_connection.Object, "my-queue", "job-id"); // Assert using (var messageQueue = CleanMsmqQueueAttribute.GetMessageQueue("my-queue")) using (var transaction = new MessageQueueTransaction()) { transaction.Begin(); var message = messageQueue.Receive(TimeSpan.FromSeconds(5), transaction); Assert.Equal("job-id", message.Label); transaction.Commit(); } } [Fact, CleanMsmqQueue("my-queue")] public void Enqueue_AddsAJob_WhenIdIsLongValue() { // Arrange var queue = CreateQueue(MsmqTransactionType.Internal); // Act queue.Enqueue(_connection.Object, "my-queue", (int.MaxValue + 1L).ToString()); // Assert using (var messageQueue = CleanMsmqQueueAttribute.GetMessageQueue("my-queue")) using (var transaction = new MessageQueueTransaction()) { transaction.Begin(); var message = messageQueue.Receive(TimeSpan.FromSeconds(5), transaction); Assert.Equal((int.MaxValue + 1L).ToString(), message.Label); transaction.Commit(); } } [Fact, CleanMsmqQueue("my-queue")] public void Dequeue_ReturnsFetchedJob_WithJobId() { MsmqUtils.EnqueueJobId("my-queue", "job-id"); var queue = CreateQueue(MsmqTransactionType.Internal); var fetchedJob = queue.Dequeue(new[] { "my-queue" }, _token); Assert.Equal("job-id", fetchedJob.JobId); } [Fact, CleanMsmqQueue("my-queue")] public void Dequeue_ThrowsCanceledException_WhenTokenHasBeenCancelled() { var queue = CreateQueue(MsmqTransactionType.Internal); var token = new CancellationToken(true); Assert.Throws( () => queue.Dequeue(new[] { "my-queue" }, token)); } [Fact, CleanMsmqQueue("queue-1", "queue-2")] public void Dequeue_ReturnsFetchedJob_FromOtherQueues_IfFirstAreEmpty() { MsmqUtils.EnqueueJobId("queue-2", "job-id"); var queue = CreateQueue(MsmqTransactionType.Internal); var fetchedJob = queue.Dequeue(new[] { "queue-1", "queue-2" }, _token); Assert.Equal("job-id", fetchedJob.JobId); } [Fact, CleanMsmqQueue("my-queue")] public void Dequeue_MakesJobInvisibleForOtherFetchers() { // Arrange MsmqUtils.EnqueueJobId("my-queue", "job-id"); var queue = CreateQueue(MsmqTransactionType.Internal); // Act var fetchedJob = queue.Dequeue(new[] { "my-queue" }, _token); // Assert Assert.NotNull(fetchedJob); var exception = Assert.Throws( () => MsmqUtils.DequeueJobId("my-queue", TimeSpan.FromSeconds(1))); Assert.Equal(MessageQueueErrorCode.IOTimeout, exception.MessageQueueErrorCode); } [Fact, CleanMsmqQueue("my-queue")] public void RemoveFromQueue_OnFetchedJob_RemovesTheJobCompletely() { // Arrange MsmqUtils.EnqueueJobId("my-queue", "job-id"); var queue = CreateQueue(MsmqTransactionType.Internal); // Act using (var fetchedJob = queue.Dequeue(new[] { "my-queue" }, _token)) { fetchedJob.RemoveFromQueue(); } // Assert var exception = Assert.Throws( () => MsmqUtils.DequeueJobId("my-queue", TimeSpan.FromSeconds(5))); Assert.Equal(MessageQueueErrorCode.IOTimeout, exception.MessageQueueErrorCode); } [Fact, CleanMsmqQueue("my-queue")] public void DisposeWithoutRemoval_OnFetchedJob_ReturnsTheJobToTheQueue() { // Arrange MsmqUtils.EnqueueJobId("my-queue", "job-id"); var queue = CreateQueue(MsmqTransactionType.Internal); // Act var fetchedJob = queue.Dequeue(new[] { "my-queue" }, _token); fetchedJob.Dispose(); // Assert var jobId = MsmqUtils.DequeueJobId("my-queue", TimeSpan.FromSeconds(5)); Assert.Equal("job-id", jobId); } private static MsmqJobQueue CreateQueue(MsmqTransactionType transactionType) { return new MsmqJobQueue(CleanMsmqQueueAttribute.PathPattern, transactionType); } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/MsmqJobQueueMonitoringApiFacts.cs ================================================ using System; using System.Linq; using Xunit; namespace Hangfire.SqlServer.Msmq.Tests { public class MsmqJobQueueMonitoringApiFacts { private static readonly string PathPattern = CleanMsmqQueueAttribute.PathPattern; private static readonly string[] Queues = { "default", "critical" }; [Fact] public void Ctor_ThrowsAnException_WhenPathPatternIsNull() { var exception = Assert.Throws( () => new MsmqJobQueueMonitoringApi(null, Queues)); Assert.Equal("pathPattern", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenQueuesCollectionIsNull() { var exception = Assert.Throws( () => new MsmqJobQueueMonitoringApi(PathPattern, null)); Assert.Equal("queues", exception.ParamName); } [Fact] public void GetQueues_ReturnsAllGivenQueues() { var api = CreateMonitoringApi(); var queues = api.GetQueues(); Assert.Equal(Queues, queues); } [Fact] public void GetFetchedJobIds_ReturnsEmptyCollection() { var api = CreateMonitoringApi(); var fetchedJobIds = api.GetFetchedJobIds("", 1, 14); Assert.Empty(fetchedJobIds); } [Fact, CleanMsmqQueue("my-queue", "another-queue")] public void GetEnqueuedAndFetchedCount_ReturnsCorrectCounters() { MsmqUtils.EnqueueJobId("my-queue", "1"); MsmqUtils.EnqueueJobId("my-queue", "2"); MsmqUtils.EnqueueJobId("another-queue", "3"); var api = CreateMonitoringApi(); var result = api.GetEnqueuedAndFetchedCount("my-queue"); Assert.Equal(2, result.EnqueuedCount); Assert.Null(result.FetchedCount); } [Fact, CleanMsmqQueue("my-queue")] public void GetEnqueuedJobIds_ReturnsEmptyCollection_IfQueueIsEmpty() { var api = CreateMonitoringApi(); var result = api.GetEnqueuedJobIds("my-queue", 5, 15); Assert.Empty(result); } [Fact, CleanMsmqQueue("my-queue")] public void GetEnqueuedJobIds_ReturnsCorrectResult() { for (var i = 1; i <= 10; i++) { MsmqUtils.EnqueueJobId("my-queue", i.ToString()); } var api = CreateMonitoringApi(); var result = api.GetEnqueuedJobIds("my-queue", 3, 2).ToArray(); Assert.Equal(2, result.Length); Assert.Equal(4, result[0]); Assert.Equal(5, result[1]); } [Fact, CleanMsmqQueue("my-queue")] public void GetEnqueuedJobIds_ReturnsCorrectResult_WhenJobIdIsLongValue() { MsmqUtils.EnqueueJobId("my-queue", (int.MaxValue + 1L).ToString()); var api = CreateMonitoringApi(); var result = api.GetEnqueuedJobIds("my-queue", 0, 1).ToArray(); Assert.Single(result); Assert.Equal(int.MaxValue + 1L, result[0]); } private static MsmqJobQueueMonitoringApi CreateMonitoringApi() { return new MsmqJobQueueMonitoringApi(PathPattern, Queues); } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/MsmqJobQueueProviderFacts.cs ================================================ using Xunit; namespace Hangfire.SqlServer.Msmq.Tests { public class MsmqJobQueueProviderFacts { private static readonly string[] Queues = { "default" }; [Fact] public void GetJobQueue_ReturnsNonNullInstance() { var provider = CreateProvider(); var jobQueue = provider.GetJobQueue(); Assert.NotNull(jobQueue); } [Fact] public void GetMonitoringApi_ReturnsNonNullInstance() { var provider = CreateProvider(); var monitoring = provider.GetJobQueueMonitoringApi(); Assert.NotNull(monitoring); } private static MsmqJobQueueProvider CreateProvider() { return new MsmqJobQueueProvider( CleanMsmqQueueAttribute.PathPattern, Queues, MsmqTransactionType.Internal); } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/MsmqSqlServerStorageExtensionsFacts.cs ================================================ using System; using System.Linq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Msmq.Tests { public class MsmqSqlServerStorageExtensionsFacts { private readonly SqlServerStorage _storage; public MsmqSqlServerStorageExtensionsFacts() { _storage = new SqlServerStorage( @"Server=.\sqlexpress;Database=TheDatabase;Trusted_Connection=True;", new SqlServerStorageOptions { PrepareSchemaIfNecessary = false }); } [Fact] public void UseMsmqQueues_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => MsmqSqlServerStorageExtensions.UseMsmqQueues(null, CleanMsmqQueueAttribute.PathPattern)); Assert.Equal("storage", exception.ParamName); } [Fact] public void UseMsmqQueues_AddsMsmqJobQueueProvider() { _storage.UseMsmqQueues(CleanMsmqQueueAttribute.PathPattern); var providerTypes = _storage.QueueProviders.Select(x => x.GetType()); Assert.Contains(typeof(MsmqJobQueueProvider), providerTypes); } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/Utils/CleanMsmqQueueAttribute.cs ================================================ using System; using System.Linq; using System.Messaging; using System.Reflection; using System.Threading; using Xunit.Sdk; namespace Hangfire.SqlServer.Msmq.Tests { public class CleanMsmqQueueAttribute : BeforeAfterTestAttribute { private static readonly object GlobalLock = new object(); public static readonly string PathPattern = @".\Private$\hangfire-{0}"; private readonly string[] _queues; public CleanMsmqQueueAttribute(params string[] queues) { _queues = queues; } public override void Before(MethodInfo methodUnderTest) { Monitor.Enter(GlobalLock); foreach (var queuePath in _queues.Select(GetPath)) { if (MessageQueue.Exists(queuePath)) { MessageQueue.Delete(queuePath); } using (MessageQueue.Create(queuePath, transactional: true)) { // We just need to create it. } } } public override void After(MethodInfo methodUnderTest) { Monitor.Exit(GlobalLock); } public static MessageQueue GetMessageQueue(string queue) { return new MessageQueue(GetPath(queue)); } private static string GetPath(string queue) { return String.Format(PathPattern, queue); } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/Utils/MsmqUtils.cs ================================================ using System; using System.Messaging; // ReSharper disable PossibleNullReferenceException namespace Hangfire.SqlServer.Msmq.Tests { internal sealed class MsmqUtils { public static void EnqueueJobId(string queue, string jobId) { using (var messageQueue = CleanMsmqQueueAttribute.GetMessageQueue(queue)) using (var message = new Message { Body = jobId, Label = jobId, Formatter = new BinaryMessageFormatter() }) using (var transaction = new MessageQueueTransaction()) { transaction.Begin(); messageQueue.Send(message, transaction); transaction.Commit(); } } public static string DequeueJobId(string queue, TimeSpan timeout) { using (var messageQueue = CleanMsmqQueueAttribute.GetMessageQueue(queue)) using (var transaction = new MessageQueueTransaction()) { transaction.Begin(); using (var message = messageQueue.Receive(timeout, transaction)) { message.Formatter = new BinaryMessageFormatter(); transaction.Commit(); return (string)message.Body; } } } } } ================================================ FILE: tests/Hangfire.SqlServer.Msmq.Tests/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.2": { "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net452": "1.0.3" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[9.0.1, )", "resolved": "9.0.1", "contentHash": "U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==" }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==" }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==" }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Dapper": { "type": "Transitive", "resolved": "1.60.6", "contentHash": "mmnJNhKMeF2KhvVXDoVQlFxre8aJAo71YBJrKqFlvuqzYC2QiXUq94/GCDBJzU7paq4GqpkV2glw3308TcGibw==" }, "Microsoft.NETFramework.ReferenceAssemblies.net452": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "kuFOgilYbs29xENHlqQ6aJYa+t56u+OqHx85P7GYLVlo7HL3nsug9IQY2DoPgkOpZ2xb9btYV2EFK7Enll8S3A==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==" }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[1.60.6, )", "Hangfire.Core": "[1.0.0, )" } }, "hangfire.sqlserver.msmq": { "type": "Project", "dependencies": { "Hangfire.Core": "[1.0.0, )", "Hangfire.SqlServer": "[1.0.0, )" } } } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/CountersAggregatorFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Data.Common; using System.Linq; using System.Threading; using ReferencedDapper::Dapper; using Xunit; namespace Hangfire.SqlServer.Tests { public class CountersAggregatorFacts { [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void CountersAggregatorExecutesProperly(bool useMicrosoftDataSqlClient) { var createSql = $@" insert into [{Constants.DefaultSchema}].Counter ([Key], [Value], ExpireAt) values ('key', 1, @expireAt)"; using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { // Arrange connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddHours(1) }); var aggregator = CreateAggregator(useMicrosoftDataSqlClient); var cts = new CancellationTokenSource(); cts.Cancel(); // Act aggregator.Execute(cts.Token); // Assert Assert.Equal(1, connection.Query($"select count(*) from [{Constants.DefaultSchema}].AggregatedCounter").Single()); } } private static DbConnection CreateConnection(bool useMicrosoftDataSqlClient) { return ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient); } private static CountersAggregator CreateAggregator(bool useMicrosoftDataSqlClient) { var storage = new SqlServerStorage(() => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)); return new CountersAggregator(storage, TimeSpan.Zero); } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/ExpirationManagerFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Data.Common; using System.Linq; using System.Threading; using ReferencedDapper::Dapper; using Xunit; namespace Hangfire.SqlServer.Tests { public class ExpirationManagerFacts { private readonly CancellationTokenSource _cts = new CancellationTokenSource(); [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { Assert.Throws(() => new ExpirationManager(null, TimeSpan.Zero, TimeSpan.FromTicks(1))); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_RemovesOutdatedRecords(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { CreateExpirationEntry(connection, DateTime.UtcNow.AddMonths(-1)); var manager = CreateManager(useMicrosoftDataSqlClient); manager.Execute(_cts.Token); Assert.True(IsEntryExpired(connection)); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_DoesNotRemoveEntries_WithNoExpirationTimeSet(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { CreateExpirationEntry(connection, null); var manager = CreateManager(useMicrosoftDataSqlClient); manager.Execute(_cts.Token); Assert.False(IsEntryExpired(connection)); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_DoesNotRemoveEntries_WithFreshExpirationTime(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { CreateExpirationEntry(connection, DateTime.UtcNow.AddMonths(1)); var manager = CreateManager(useMicrosoftDataSqlClient); manager.Execute(_cts.Token); Assert.False(IsEntryExpired(connection)); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_Processes_AggregatedCounterTable(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { // Arrange var createSql = $@" insert into [{Constants.DefaultSchema}].AggregatedCounter ([Key], [Value], ExpireAt) values ('key', 1, @expireAt)"; connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) }); var manager = CreateManager(useMicrosoftDataSqlClient); // Act manager.Execute(_cts.Token); // Assert Assert.Equal(0, connection.Query($"select count(*) from [{Constants.DefaultSchema}].Counter").Single()); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_Processes_JobTable(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { // Arrange var createSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt, ExpireAt) values ('', '', getutcdate(), @expireAt)"; connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) }); var manager = CreateManager(useMicrosoftDataSqlClient); // Act manager.Execute(_cts.Token); // Assert Assert.Equal(0, connection.Query($"select count(*) from [{Constants.DefaultSchema}].Job").Single()); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_Processes_StateTable_WhenOptionIsConfigured(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { // Arrange var now = DateTime.UtcNow; var createSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, StateName, CreatedAt) values ('', '', '', getutcdate()); declare @JobId bigint; set @JobId = scope_identity(); insert into [{Constants.DefaultSchema}].State (JobId, Name, CreatedAt) values (@JobId, 'old-state-1', @createdAt1); insert into [{Constants.DefaultSchema}].State (JobId, Name, CreatedAt) values (@JobId, 'old-state-2', @createdAt2); insert into [{Constants.DefaultSchema}].State (JobId, Name, CreatedAt) values (@JobId, 'current-state', @createdAt3); declare @StateId bigint; set @StateId = scope_identity(); update [{Constants.DefaultSchema}].Job set StateId = @StateId; select @JobId as Id;"; var jobId = connection .Query(createSql, new { createdAt1 = now.AddDays(-1), createdAt2 = now.AddMonths(-1), createdAt3 = now.AddMonths(-1) }) .Single().Id; var manager = CreateManager(useMicrosoftDataSqlClient, TimeSpan.FromDays(7)); // Act manager.Execute(_cts.Token); // Assert var states = connection .Query($"select [Name] from [{Constants.DefaultSchema}].State where JobId = @jobId order by Id", new { jobId }) .ToList(); Assert.Equal("old-state-1", states[0]); Assert.Equal("current-state", states[1]); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_Processes_ListTable(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { // Arrange var createSql = $@" insert into [{Constants.DefaultSchema}].List ([Key], ExpireAt) values ('key', @expireAt)"; connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) }); var manager = CreateManager(useMicrosoftDataSqlClient); // Act manager.Execute(_cts.Token); // Assert Assert.Equal(0, connection.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single()); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_Processes_SetTable(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { // Arrange var createSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Score], [Value], ExpireAt) values ('key', 0, '', @expireAt)"; connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) }); var manager = CreateManager(useMicrosoftDataSqlClient); // Act manager.Execute(_cts.Token); // Assert Assert.Equal(0, connection.Query($"select count(*) from [{Constants.DefaultSchema}].[Set]").Single()); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Execute_Processes_HashTable(bool useMicrosoftDataSqlClient) { using (var connection = CreateConnection(useMicrosoftDataSqlClient)) { // Arrange var createSql = $@" insert into [{Constants.DefaultSchema}].Hash ([Key], [Field], [Value], ExpireAt) values ('key', 'field', '', @expireAt)"; connection.Execute(createSql, new { expireAt = DateTime.UtcNow.AddMonths(-1) }); var manager = CreateManager(useMicrosoftDataSqlClient); // Act manager.Execute(_cts.Token); // Assert Assert.Equal(0, connection.Query($"select count(*) from [{Constants.DefaultSchema}].Hash").Single()); } } private static void CreateExpirationEntry(DbConnection connection, DateTime? expireAt) { var insertSql = $@" insert into [{Constants.DefaultSchema}].AggregatedCounter ([Key], [Value], [ExpireAt]) values (N'key', 1, @expireAt)"; connection.Execute(insertSql, new { expireAt }); } private static bool IsEntryExpired(DbConnection connection) { var count = connection.Query( $"select count(*) from [{Constants.DefaultSchema}].AggregatedCounter where [Key] = N'key'").Single(); return count == 0; } private DbConnection CreateConnection(bool useMicrosoftDataSqlClient) { return ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient); } private ExpirationManager CreateManager(bool useMicrosoftDataSqlClient, TimeSpan? stateExpirationTimeout = null) { var storage = new SqlServerStorage(() => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)); return new ExpirationManager(storage, stateExpirationTimeout ?? TimeSpan.Zero, TimeSpan.FromTicks(1)); } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/GlobalTestsConfiguration.cs ================================================ using Xunit; [assembly: CollectionBehavior(MaxParallelThreads = 1)] ================================================ FILE: tests/Hangfire.SqlServer.Tests/Hangfire.SqlServer.Tests.csproj ================================================  net452;net461;net6.0;net8.0 0618 ReferencedDapper ================================================ FILE: tests/Hangfire.SqlServer.Tests/PersistentJobQueueProviderCollectionFacts.cs ================================================ using System; using System.Linq; using Moq; using Xunit; namespace Hangfire.SqlServer.Tests { public class PersistentJobQueueProviderCollectionFacts { private static readonly string[] Queues = { "default", "critical" }; private readonly Mock _defaultProvider; private readonly Mock _provider; public PersistentJobQueueProviderCollectionFacts() { _defaultProvider = new Mock(); _provider = new Mock(); } [Fact] public void Ctor_ThrowsAnException_WhenDefaultProviderIsNull() { Assert.Throws( () => new PersistentJobQueueProviderCollection(null)); } [Fact] public void Enumeration_IncludesTheDefaultProvider() { var collection = CreateCollection(); var result = collection.ToArray(); Assert.Single(result); Assert.Same(_defaultProvider.Object, result[0]); } [Fact] public void GetProvider_ReturnsTheDefaultProvider_WhenProviderCanNotBeResolvedByQueue() { var collection = CreateCollection(); var provider = collection.GetProvider("queue"); Assert.Same(_defaultProvider.Object, provider); } [Fact] public void Add_ThrowsAnException_WhenProviderIsNull() { var collection = CreateCollection(); var exception = Assert.Throws( () => collection.Add(null, Queues)); Assert.Equal("provider", exception.ParamName); } [Fact] public void Add_ThrowsAnException_WhenQueuesCollectionIsNull() { var collection = CreateCollection(); var exception = Assert.Throws( () => collection.Add(_provider.Object, null)); Assert.Equal("queues", exception.ParamName); } [Fact] public void Enumeration_ContainsAddedProvider() { var collection = CreateCollection(); collection.Add(_provider.Object, Queues); Assert.Contains(_provider.Object, collection); } [Fact] public void GetProvider_CanBeResolved_ByAnyQueue() { var collection = CreateCollection(); collection.Add(_provider.Object, Queues); var provider1 = collection.GetProvider("default"); var provider2 = collection.GetProvider("critical"); Assert.NotSame(_defaultProvider.Object, provider1); Assert.Same(provider1, provider2); } private PersistentJobQueueProviderCollection CreateCollection() { return new PersistentJobQueueProviderCollection(_defaultProvider.Object); } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerConnectionFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Collections.Generic; using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Threading; using ReferencedDapper::Dapper; using Hangfire.Common; using Hangfire.Server; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable PossibleNullReferenceException // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Tests { public class SqlServerConnectionFacts { private readonly Mock _queue; private readonly PersistentJobQueueProviderCollection _providers; public SqlServerConnectionFacts() { _queue = new Mock(); var provider = new Mock(); provider.Setup(x => x.GetJobQueue()) .Returns(_queue.Object); _providers = new PersistentJobQueueProviderCollection(provider.Object); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new SqlServerConnection(null)); Assert.Equal("storage", exception.ParamName); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void FetchNextJob_DelegatesItsExecution_ToTheQueue(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var token = new CancellationToken(); var queues = new[] { "default" }; connection.FetchNextJob(queues, token); _queue.Verify(x => x.Dequeue(queues, token)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void FetchNextJob_Throws_IfMultipleProvidersResolved(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var token = new CancellationToken(); var anotherProvider = new Mock(); _providers.Add(anotherProvider.Object, new [] { "critical" }); Assert.Throws( () => connection.FetchNextJob(new[] { "critical", "default" }, token)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void CreateWriteTransaction_ReturnsNonNullInstance(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var transaction = connection.CreateWriteTransaction(); Assert.NotNull(transaction); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AcquireLock_ReturnsNonNullInstance(bool useMicrosoftDataSqlClient) { UseConnection(connection => { using (var @lock = connection.AcquireDistributedLock("1", TimeSpan.FromSeconds(1))) { Assert.NotNull(@lock); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AcquireDistributedLock_ThrowsAnException_WhenResourceIsNullOrEmpty(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.AcquireDistributedLock("", TimeSpan.FromMinutes(5))); Assert.Equal("resource", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AcquireDistributedLock_AcquiresExclusiveApplicationLock_OnSession(bool useMicrosoftDataSqlClient) { using (var sql = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { var storage = new SqlServerStorage(sql); using (var connection = new SqlServerConnection(storage)) using (connection.AcquireDistributedLock("hello", TimeSpan.FromMinutes(5))) { var lockMode = sql.Query( $"select applock_mode('public', '{Constants.DefaultSchema}:hello', 'session')").Single(); Assert.Equal("Exclusive", lockMode); } } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AcquireDistributedLock_ThrowsAnException_IfLockCanNotBeGranted(bool useMicrosoftDataSqlClient) { var releaseLock = new ManualResetEventSlim(false); var lockAcquired = new ManualResetEventSlim(false); var thread = new Thread( () => UseConnection(connection1 => { using (connection1.AcquireDistributedLock("exclusive", TimeSpan.Zero)) { lockAcquired.Set(); releaseLock.Wait(); } }, useMicrosoftDataSqlClient)); thread.Start(); lockAcquired.Wait(); UseConnection(connection2 => { Assert.Throws( () => { using (connection2.AcquireDistributedLock("exclusive", TimeSpan.FromSeconds(1))) { } }); }, useMicrosoftDataSqlClient); releaseLock.Set(); thread.Join(); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AcquireDistributedLock_Dispose_ReleasesExclusiveApplicationLock(bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var distributedLock = connection.AcquireDistributedLock("hello", TimeSpan.FromMinutes(5)); distributedLock.Dispose(); var lockMode = sql.Query( $"select applock_mode('public', '{Constants.DefaultSchema}:hello', 'session')").Single(); Assert.Equal("NoLock", lockMode); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AcquireDistributedLock_IsReentrant_FromTheSameConnection_OnTheSameResource(bool useMicrosoftDataSqlClient) { UseConnection(connection => { using (connection.AcquireDistributedLock("hello", TimeSpan.FromMinutes(5))) using (connection.AcquireDistributedLock("hello", TimeSpan.FromMinutes(5))) { Assert.True(true); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void CreateExpiredJob_ThrowsAnException_WhenJobIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.CreateExpiredJob( null, new Dictionary(), DateTime.UtcNow, TimeSpan.Zero)); Assert.Equal("job", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void CreateExpiredJob_ThrowsAnException_WhenParametersCollectionIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("hello")), null, DateTime.UtcNow, TimeSpan.Zero)); Assert.Equal("parameters", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CreatesAJobInTheStorage_AndSetsItsParameters(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, { "Key3", "Value3" } }, createdAt, TimeSpan.FromDays(1)); Assert.NotNull(jobId); Assert.NotEmpty(jobId); var sqlJob = sql.Query($"select * from [{Constants.DefaultSchema}].Job").Single(); Assert.Equal(jobId, sqlJob.Id.ToString()); Assert.Equal(createdAt, sqlJob.CreatedAt); Assert.Null((int?) sqlJob.StateId); Assert.Null((string) sqlJob.StateName); var invocationData = InvocationData.DeserializePayload((string)sqlJob.InvocationData); invocationData.Arguments = sqlJob.Arguments; var job = invocationData.Deserialize(); Assert.Equal(typeof(SqlServerConnectionFacts), job.Type); Assert.Equal("SampleMethod", job.Method.Name); Assert.Equal("Hello", job.Args[0]); Assert.True(createdAt.AddDays(1).AddMinutes(-1) < sqlJob.ExpireAt); Assert.True(sqlJob.ExpireAt < createdAt.AddDays(1).AddMinutes(1)); var parameters = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string) x.Name, x => (string) x.Value); Assert.Equal("Value1", parameters["Key1"]); Assert.Equal("Value2", parameters["Key2"]); Assert.Equal("Value3", parameters["Key3"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateParametersWithNonNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Equal("Value1", parameters["Key1"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateTwoParametersWithNonNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Equal("Value1", parameters["Key1"]); Assert.Equal("Value2", parameters["Key2"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateThreeParametersWithNonNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, { "Key3", "Value3" } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Equal("Value1", parameters["Key1"]); Assert.Equal("Value2", parameters["Key2"]); Assert.Equal("Value3", parameters["Key3"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateFourParametersWithNonNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, { "Key3", "Value3" }, { "Key4", "Value4" } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Equal("Value1", parameters["Key1"]); Assert.Equal("Value2", parameters["Key2"]); Assert.Equal("Value3", parameters["Key3"]); Assert.Equal("Value4", parameters["Key4"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateManyParametersWithNonNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" }, { "Key3", "Value3" }, { "Key4", "Value4" }, { "Key5", "Value5" } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Equal("Value1", parameters["Key1"]); Assert.Equal("Value2", parameters["Key2"]); Assert.Equal("Value3", parameters["Key3"]); Assert.Equal("Value4", parameters["Key4"]); Assert.Equal("Value5", parameters["Key5"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateParametersWithNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", null } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Null(parameters["Key1"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateTwoParametersWithNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", null }, { "Key2", null } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Null(parameters["Key1"]); Assert.Null(parameters["Key2"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateThreeParametersWithNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", null }, { "Key2", null }, { "Key3", null } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Null(parameters["Key1"]); Assert.Null(parameters["Key2"]); Assert.Null(parameters["Key3"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateFourParametersWithNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", null }, { "Key2", null }, { "Key3", null }, { "Key4", null } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Null(parameters["Key1"]); Assert.Null(parameters["Key2"]); Assert.Null(parameters["Key3"]); Assert.Null(parameters["Key4"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateManyParametersWithNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary { { "Key1", null }, { "Key2", null }, { "Key3", null }, { "Key4", null }, { "Key5", null } }, createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Null(parameters["Key1"]); Assert.Null(parameters["Key2"]); Assert.Null(parameters["Key3"]); Assert.Null(parameters["Key4"]); Assert.Null(parameters["Key5"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void CreateExpiredJob_CanCreateJobWithoutParameters(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary(), createdAt, TimeSpan.FromDays(1)); var parameters = sql .Query($"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id", new { id = jobId }) .ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Empty(parameters); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobData_ThrowsAnException_WhenJobIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection( connection => Assert.Throws(() => connection.GetJobData(null)), useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobData_ReturnsNull_WhenThereIsNoSuchJob(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetJobData("1"); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobData_ReturnsNull_WhenIdentifierCanNotBeParsedAsLong(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetJobData("some-non-long-id"); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobData_ReturnsResult_WhenJobExists(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, StateName, CreatedAt) values (@invocationData, @arguments, @stateName, getutcdate()) select scope_identity() as Id"; UseConnections((sql, connection) => { var job = Job.FromExpression(() => SampleMethod("wrong")); var jobId = sql.Query( arrangeSql, new { invocationData = JobHelper.ToJson(InvocationData.Serialize(job)), stateName = "Succeeded", arguments = "['Arguments']" }).Single(); var result = connection.GetJobData(((long)jobId.Id).ToString()); Assert.NotNull(result); Assert.NotNull(result.Job); Assert.Equal("Succeeded", result.State); Assert.Equal("Arguments", result.Job.Args[0]); Assert.Null(result.LoadException); Assert.True(DateTime.UtcNow.AddMinutes(-1) < result.CreatedAt); Assert.True(result.CreatedAt < DateTime.UtcNow.AddMinutes(1)); Assert.Empty(result.ParametersSnapshot); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobData_ReturnsResultWithParameters_WhenJobAndParametersExist(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, StateName, CreatedAt) values (@invocationData, @arguments, @stateName, getutcdate()) declare @id bigint = scope_identity(); insert into [{Constants.DefaultSchema}].JobParameter ([JobId], [Name], [Value]) values (@id, N'Param1', N'Value1'), (@id, N'Param2', 'Value2') select @id as Id"; UseConnections((sql, connection) => { var job = Job.FromExpression(() => SampleMethod("wrong")); var jobId = sql.Query( arrangeSql, new { invocationData = JobHelper.ToJson(InvocationData.Serialize(job)), stateName = "Succeeded", arguments = "['Arguments']" }).Single(); var result = connection.GetJobData(((long)jobId.Id).ToString()); Assert.NotNull(result); Assert.Equal("SampleMethod", result.Job.Method.Name); Assert.Equal(2, result.ParametersSnapshot.Count); Assert.Equal("Value1", result.ParametersSnapshot["Param1"]); Assert.Equal("Value2", result.ParametersSnapshot["Param2"]); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetStateData_ThrowsAnException_WhenJobIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection( connection => Assert.Throws(() => connection.GetStateData(null)), useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetStateData_ReturnsNull_IfThereIsNoSuchState(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetStateData("1"); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetStateData_ReturnsNull_WhenIdentifierCanNotBeParsedAsLong(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetStateData("some-non-long-id"); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetStateData_ReturnsCorrectData(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, StateName, CreatedAt) values ('', '', '', getutcdate()); declare @JobId bigint; set @JobId = scope_identity(); insert into [{Constants.DefaultSchema}].State (JobId, Name, CreatedAt) values (@JobId, 'old-state', getutcdate()); insert into [{Constants.DefaultSchema}].State (JobId, Name, Reason, Data, CreatedAt) values (@JobId, @name, @reason, @data, getutcdate()); declare @StateId bigint; set @StateId = scope_identity(); update [{Constants.DefaultSchema}].Job set StateId = @StateId; select @JobId as Id;"; UseConnections((sql, connection) => { var data = new Dictionary { { "Key", "Value" } }; var jobId = (long)sql.Query( arrangeSql, new { name = "Name", reason = "Reason", @data = JobHelper.ToJson(data) }).Single().Id; var result = connection.GetStateData(jobId.ToString()); Assert.NotNull(result); Assert.Equal("Name", result.Name); Assert.Equal("Reason", result.Reason); Assert.Equal("Value", result.Data["Key"]); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetStateData_ReturnsCorrectData_WhenPropertiesAreCamelcased(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, StateName, CreatedAt) values ('', '', '', getutcdate()); declare @JobId bigint; set @JobId = scope_identity(); insert into [{Constants.DefaultSchema}].State (JobId, Name, CreatedAt) values (@JobId, 'old-state', getutcdate()); insert into [{Constants.DefaultSchema}].State (JobId, Name, Reason, Data, CreatedAt) values (@JobId, @name, @reason, @data, getutcdate()); declare @StateId bigint; set @StateId = scope_identity(); update [{Constants.DefaultSchema}].Job set StateId = @StateId; select @JobId as Id;"; UseConnections((sql, connection) => { var data = new Dictionary { { "key", "Value" } }; var jobId = (long)sql.Query( arrangeSql, new { name = "Name", reason = "Reason", @data = JobHelper.ToJson(data) }).Single().Id; var result = connection.GetStateData(jobId.ToString()); Assert.NotNull(result); Assert.Equal("Value", result.Data["Key"]); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobData_ReturnsJobLoadException_IfThereWasADeserializationException(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, StateName, CreatedAt) values (@invocationData, @arguments, @stateName, getutcdate()) select scope_identity() as Id"; UseConnections((sql, connection) => { var jobId = sql.Query( arrangeSql, new { invocationData = JobHelper.ToJson(new InvocationData(null, null, null, null)), stateName = "Succeeded", arguments = "['Arguments']" }).Single(); var result = connection.GetJobData(((long)jobId.Id).ToString()); Assert.NotNull(result.LoadException); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetParameter_ThrowsAnException_WhenJobIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.SetJobParameter(null, "name", "value")); Assert.Equal("id", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetParameter_ThrowsAnException_WhenNameIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.SetJobParameter("1", null, "value")); Assert.Equal("name", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetParameters_CreatesNewParameter_WhenParameterWithTheGivenNameDoesNotExists(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnections((sql, connection) => { var job = sql.Query(arrangeSql).Single(); string jobId = job.Id.ToString(); connection.SetJobParameter(jobId, "Name", "Value"); var parameter = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id and Name = @name", new { id = jobId, name = "Name" }).Single(); Assert.Equal("Value", parameter.Value); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetParameter_UpdatesValue_WhenParameterWithTheGivenName_AlreadyExists(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnections((sql, connection) => { var job = sql.Query(arrangeSql).Single(); string jobId = job.Id.ToString(); connection.SetJobParameter(jobId, "Name", "Value"); connection.SetJobParameter(jobId, "Name", "AnotherValue"); var parameter = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id and Name = @name", new { id = jobId, name = "Name" }).Single(); Assert.Equal("AnotherValue", parameter.Value); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetParameter_CanAcceptNulls_AsValues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnections((sql, connection) => { var job = sql.Query(arrangeSql).Single(); string jobId = job.Id.ToString(); connection.SetJobParameter(jobId, "Name", null); var parameter = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id and Name = @name", new { id = jobId, name = "Name" }).Single(); Assert.Equal((string) null, parameter.Value); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void SetJobParameter_WithIgnoreDupKeyOption_InsertsNonExistingValue(bool useBatching, bool useMicrosoftDataSqlClient) { try { UseConnections((sql, connection) => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[JobParameter] REBUILD WITH (IGNORE_DUP_KEY = ON)"); string jobId = sql.Query($@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id").Single().Id.ToString(); connection.SetJobParameter(jobId, "Name", "Value"); var parameter = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @jobId and Name = N'Name'", new { jobId }).Single(); Assert.Equal("Value", parameter.Value); }, useBatching, useMicrosoftDataSqlClient); } finally { UseConnections((sql, _) => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[JobParameter] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useBatching, useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void SetJobParameter_WithIgnoreDupKeyOption_UpdatesExistingValue_WhenIgnoreDupKeyOptionIsSet(bool setIgnoreDupKey, bool useMicrosoftDataSqlClient) { try { UseConnections((sql, connection) => { sql.Execute("SET XACT_ABORT ON"); var onOrOff = setIgnoreDupKey ? "ON" : "OFF"; sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[JobParameter] REBUILD WITH (IGNORE_DUP_KEY = {onOrOff})"); string jobId1 = sql.Query($@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id").Single().Id.ToString(); string jobId2 = sql.Query($@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id").Single().Id.ToString(); sql.Execute( $@"insert into [{Constants.DefaultSchema}].[JobParameter] (JobId, Name, Value) values (@jobId1, N'Name1', N'Value1'), (@jobId1, N'Name2', N'Value1'), (@jobId2, N'Name1', N'Value1')", new { jobId1, jobId2 }); connection.SetJobParameter(jobId1, "Name1", "Value2"); var parameters1 = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @jobId1", new { jobId1 }).ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Equal("Value2", parameters1["Name1"]); Assert.Equal("Value1", parameters1["Name2"]); var parameters2 = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @jobId2", new { jobId2 }).ToDictionary(x => (string)x.Name, x => (string)x.Value); Assert.Equal("Value1", parameters2["Name1"]); }, useBatching: false, useMicrosoftDataSqlClient); } finally { UseConnections((sql, _) => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[JobParameter] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useBatching: false, useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetParameter_ThrowsAnException_WhenJobIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetJobParameter(null, "hello")); Assert.Equal("id", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetParameter_ThrowsAnException_WhenNameIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetJobParameter("1", null)); Assert.Equal("name", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetParameter_ReturnsNull_WhenParameterDoesNotExists(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var value = connection.GetJobParameter("1", "hello"); Assert.Null(value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetParameter_ReturnsNull_WhenJobIdCanNotBeParsedAsLong(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetJobParameter("some-non-long-id", "name"); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetParameter_ReturnsParameterValue_WhenJobExists(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" declare @id bigint insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) set @id = scope_identity() insert into [{Constants.DefaultSchema}].JobParameter (JobId, Name, Value) values (@id, @name, @value) select @id"; UseConnections((sql, connection) => { var id = sql.Query( arrangeSql, new { name = "name", value = "value" }).Single(); var value = connection.GetJobParameter(id.ToString(), "name"); Assert.Equal("value", value); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetFirstByLowestScoreFromSet(null, 0, 1)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ThrowsAnException_ToScoreIsLowerThanFromScore(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetFirstByLowestScoreFromSet("key", 0, -1)); Assert.Equal("toScore", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ReturnsNull_WhenTheKeyDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetFirstByLowestScoreFromSet( "key", 0, 1); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ThrowsArgException_WhenRequestingLessThanZero(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetFirstByLowestScoreFromSet("key", 0, 1, -1)); Assert.Equal("count", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ReturnsEmpty_WhenNoneExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetFirstByLowestScoreFromSet("key", 0, 1, 10); Assert.Empty(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ReturnsN_WhenMoreThanNExist(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], Score, Value) values ('key', 1.0, '1234'), ('key', -1.0, '567'), ('key', -5.0, '890'), ('another-key', -2.0, 'abcd')"; UseConnections((sql, connection) => { sql.Execute(arrangeSql); var result = connection.GetFirstByLowestScoreFromSet("key", -10.0, 10.0, 2); Assert.Equal(2, result.Count); Assert.Equal("890", result.ElementAt(0)); Assert.Equal("567", result.ElementAt(1)); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ReturnsN_WhenMoreThanNExist_And_RequestedCountIsGreaterThanN(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], Score, Value) values ('key', 1.0, '1234'), ('key', -1.0, '567'), ('key', -5.0, '890'), ('another-key', -2.0, 'abcd')"; UseConnections((sql, connection) => { sql.Execute(arrangeSql); var result = connection.GetFirstByLowestScoreFromSet("another-key", -10.0, 10.0, 5); Assert.Single(result); Assert.Equal("abcd", result.First()); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetFirstByLowestScoreFromSet_ReturnsTheValueWithTheLowestScore(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], Score, Value) values ('key', 1.0, '1.0'), ('key', -1.0, '-1.0'), ('key', -5.0, '-5.0'), ('another-key', -2.0, '-2.0')"; UseConnections((sql, connection) => { sql.Execute(arrangeSql); var result = connection.GetFirstByLowestScoreFromSet("key", -1.0, 3.0); Assert.Equal("-1.0", result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AnnounceServer_ThrowsAnException_WhenServerIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.AnnounceServer(null, new ServerContext())); Assert.Equal("serverId", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AnnounceServer_ThrowsAnException_WhenContextIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.AnnounceServer("server", null)); Assert.Equal("context", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void AnnounceServer_CreatesOrUpdatesARecord(bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { var context1 = new ServerContext { Queues = new[] { "critical", "default" }, WorkerCount = 4 }; connection.AnnounceServer("server", context1); var server = sql.Query($"select * from [{Constants.DefaultSchema}].Server").Single(); Assert.Equal("server", server.Id); Assert.True(((string)server.Data).StartsWith( "{\"WorkerCount\":4,\"Queues\":[\"critical\",\"default\"],\"StartedAt\":"), server.Data); Assert.NotNull(server.LastHeartbeat); Assert.True(DateTime.UtcNow.AddHours(-1) < server.LastHeartbeat && server.LastHeartbeat < DateTime.UtcNow.AddHours(1)); var context2 = new ServerContext { Queues = new[] { "default" }, WorkerCount = 1000 }; connection.AnnounceServer("server", context2); var sameServer = sql.Query($"select * from [{Constants.DefaultSchema}].Server").Single(); Assert.Equal("server", sameServer.Id); Assert.Contains("1000", sameServer.Data); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RemoveServer_ThrowsAnException_WhenServerIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection( connection => Assert.Throws(() => connection.RemoveServer(null)), useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RemoveServer_RemovesAServerRecord(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Server (Id, Data, LastHeartbeat) values ('Server1', '', getutcdate()), ('Server2', '', getutcdate())"; UseConnections((sql, connection) => { sql.Execute(arrangeSql); connection.RemoveServer("Server1"); var server = sql.Query($"select * from [{Constants.DefaultSchema}].Server").Single(); Assert.NotEqual("Server1", server.Id, StringComparer.OrdinalIgnoreCase); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Heartbeat_ThrowsAnException_WhenServerIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection( connection => Assert.Throws(() => connection.Heartbeat(null)), useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Heartbeat_UpdatesLastHeartbeat_OfTheServerWithGivenId(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Server (Id, Data, LastHeartbeat) values ('server1', '', '2012-12-12 12:12:12'), ('server2', '', '2012-12-12 12:12:12')"; UseConnections((sql, connection) => { sql.Execute(arrangeSql); connection.Heartbeat("server1"); var servers = sql.Query($"select * from [{Constants.DefaultSchema}].Server") .ToDictionary(x => (string)x.Id, x => (DateTime)x.LastHeartbeat); Assert.NotEqual(2012, servers["server1"].Year); Assert.Equal(2012, servers["server2"].Year); Assert.True(DateTime.UtcNow.AddHours(-1) < servers["server1"] && servers["server1"] < DateTime.UtcNow.AddHours(1)); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RemoveTimedOutServers_ThrowsAnException_WhenTimeOutIsNegative(bool useMicrosoftDataSqlClient) { UseConnection( connection => Assert.Throws(() => connection.RemoveTimedOutServers(TimeSpan.FromMinutes(-5))), useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RemoveTimedOutServers_DoItsWorkPerfectly(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Server (Id, Data, LastHeartbeat) values (@id, '', @heartbeat)"; UseConnections((sql, connection) => { sql.Execute( arrangeSql, new[] { new { id = "server1", heartbeat = DateTime.UtcNow.AddDays(-1) }, new { id = "server2", heartbeat = DateTime.UtcNow.AddHours(-12) } }); connection.RemoveTimedOutServers(TimeSpan.FromHours(15)); var liveServer = sql.Query($"select * from [{Constants.DefaultSchema}].Server").Single(); Assert.Equal("server2", liveServer.Id); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllItemsFromSet_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection( connection => Assert.Throws(() => connection.GetAllItemsFromSet(null)), useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllItemsFromSet_ReturnsEmptyCollection_WhenKeyDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetAllItemsFromSet("some-set"); Assert.NotNull(result); Assert.Empty(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllItemsFromSet_ReturnsAllItems(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], Score, Value) values (@key, 0.0, @value)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "some-set", value = "1" }, new { key = "some-set", value = "2" }, new { key = "another-set", value = "3" } }); // Act var result = connection.GetAllItemsFromSet("some-set"); // Assert Assert.Equal(2, result.Count); Assert.Contains("1", result); Assert.Contains("2", result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.SetRangeInHash(null, new Dictionary())); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.SetRangeInHash("some-hash", null)); Assert.Equal("keyValuePairs", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetRangeInHash_ThrowsSqlException_WhenKeyIsTooLong(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.ThrowsAny( () => connection.SetRangeInHash( "123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_12345", new Dictionary { { "field", "value" } })); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void SetRangeInHash_MergesAllRecords(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { connection.SetRangeInHash("some-hash", new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" } }); var result = sql.Query( $"select * from [{Constants.DefaultSchema}].Hash where [Key] = @key", new { key = "some-hash" }) .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("Value1", result["Key1"]); Assert.Equal("Value2", result["Key2"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void SetRangeInHash_CanCreateFieldsWithNullValues(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { connection.SetRangeInHash("some-hash", new Dictionary { { "Key1", null } }); var result = sql.Query( $"select * from [{Constants.DefaultSchema}].Hash where [Key] = @key", new { key = "some-hash" }) .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Null(result["Key1"]); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void SetRangeInHash_ReleasesTheAcquiredLock(bool useBatching, bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { connection.SetRangeInHash("some-hash", new Dictionary { { "Key", "Value" } }); var result = sql.QuerySingle($"select APPLOCK_MODE( 'public' , 'HangFire:Hash:Lock' , 'Session' )"); Assert.Equal("NoLock", result); }, useBatching, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false), InlineData(false, true)] [InlineData(true, false), InlineData(true, true)] public void SetRangeInHash_WithIgnoreDupKeyOption_InsertsNonExistingValue(bool useBatching, bool useMicrosoftDataSqlClient) { try { UseConnections((sql, connection) => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"); connection.SetRangeInHash("some-hash", new Dictionary { { "key", "value" } }); var result = sql .Query($"select * from [{Constants.DefaultSchema}].Hash where [Key] = N'some-hash'") .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("value", result["key"]); }, useBatching, useMicrosoftDataSqlClient); } finally { UseConnections((sql, _) => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useBatching, useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [InlineData(false, false, false), InlineData(false, false, true)] [InlineData(false, true, false), InlineData(false, true, true)] [InlineData( true, false, false), InlineData( true, false, true)] [InlineData( true, true, false), InlineData( true, true, true)] public void SetRangeInHash_WithIgnoreDupKeyOption_UpdatesExistingValue_WhenIgnoreDupKeyOptionIsSet(bool setIgnoreDupKey, bool useBatching, bool useMicrosoftDataSqlClient) { try { UseConnections((sql, connection) => { var onOrOff = setIgnoreDupKey ? "ON" : "OFF"; sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = {onOrOff})"); sql.Execute($@"insert into [{Constants.DefaultSchema}].Hash([Key], Field, Value) VALUES (N'some-hash', N'key1', N'value1'), (N'some-hash', N'key2', N'value1'), (N'othr-hash', N'key1', N'value1')"); connection.SetRangeInHash("some-hash", new Dictionary { { "key1", "value2" } }); var someResult = sql .Query($"select * from [{Constants.DefaultSchema}].Hash where [Key] = N'some-hash'") .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("value2", someResult["key1"]); Assert.Equal("value1", someResult["key2"]); var othrResult = sql .Query($"select * from [{Constants.DefaultSchema}].Hash where [Key] = N'othr-hash'") .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("value1", othrResult["key1"]); }, useBatching, useMicrosoftDataSqlClient); } finally { UseConnections((sql, _) => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useBatching, useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllEntriesFromHash_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection( connection => Assert.Throws(() => connection.GetAllEntriesFromHash(null)), useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllEntriesFromHash_ReturnsNull_IfHashDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetAllEntriesFromHash("some-hash"); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllEntriesFromHash_ReturnsAllKeysAndTheirValues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Hash ([Key], [Field], [Value]) values (@key, @field, @value)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "some-hash", field = "Key1", value = "Value1" }, new { key = "some-hash", field = "Key2", value = "Value2" }, new { key = "another-hash", field = "Key3", value = "Value3" } }); // Act var result = connection.GetAllEntriesFromHash("some-hash"); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count); Assert.Equal("Value1", result["Key1"]); Assert.Equal("Value2", result["Key2"]); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws( () => connection.GetSetCount(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_ReturnsZero_WhenSetDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetSetCount("my-set"); Assert.Equal(0, result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_ReturnsNumberOfElements_InASet(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@key, @value, 0.0)"; UseConnections((sql, connection) => { sql.Execute(arrangeSql, new List { new { key = "set-1", value = "value-1" }, new { key = "set-2", value = "value-1" }, new { key = "set-1", value = "value-2" } }); var result = connection.GetSetCount("set-1"); Assert.Equal(2, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_Limited_ThrowsAnException_WhenKeysArgumentIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetSetCount((IEnumerable) null, 10)); Assert.Equal("keys", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_Limited_ThrowsAnException_WhenLimitIsNegative(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetSetCount(Enumerable.Empty(), -10)); Assert.Equal("limit", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_Limited_ReturnsZero_WhenSetsArgumentIsEmpty(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetSetCount(Enumerable.Empty(), 10); Assert.Equal(0, result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_Limited_ReturnsZero_WhenGivenSetsDoNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetSetCount(new [] { "set-1", "set-2" }.AsEnumerable(), 10); Assert.Equal(0, result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_Limited_ReturnsTheSum_OfGivenSetCardinalities(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@Key, @Value, 0.0)"; UseConnections((sql, connection) => { sql.Execute(arrangeSql, new List { new { Key = "set-1", Value = "1" }, new { Key = "set-1", Value = "2" }, new { Key = "set-2", Value = "2" }, new { Key = "set-2", Value = "3" }, new { Key = "set-3", Value = "1" } }); var result = connection.GetSetCount(new [] { "set-1", "set-2" }.AsEnumerable(), 10); Assert.Equal(4, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetCount_Limited_LimitValue_IsConsidered(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@Key, @Value, 0.0)"; UseConnections((sql, connection) => { sql.Execute(arrangeSql, new List { new { Key = "set-1", Value = "1" }, new { Key = "set-1", Value = "2" }, new { Key = "set-2", Value = "2" }, new { Key = "set-2", Value = "3" }, new { Key = "set-3", Value = "1" } }); var result = connection.GetSetCount(new [] { "set-1", "set-2", "set-4" }.AsEnumerable(), 2); Assert.Equal(2, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetRangeFromSet_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws(() => connection.GetRangeFromSet(null, 0, 1)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetRangeFromSet_ReturnsPagedElements_SortedByScore(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@Key, @Value, @Score)"; UseConnections((sql, connection) => { sql.Execute(arrangeSql, new List { new { Key = "set-1", Value = "4", Score = 4.0D }, new { Key = "set-2", Value = "4", Score = 4.0D }, new { Key = "set-1", Value = "6", Score = 6.0D }, new { Key = "set-1", Value = "2", Score = 2.0D }, new { Key = "set-1", Value = "3", Score = 3.0D }, new { Key = "set-1", Value = "5", Score = 5.0D }, new { Key = "set-1", Value = "1", Score = 1.0D }, }); var result = connection.GetRangeFromSet("set-1", 2, 4); Assert.Equal(new [] { "3", "4", "5" }, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetCounter_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws( () => connection.GetCounter(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetContains_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetSetContains(null, "value")); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetContains_ThrowsAnException_WhenValueIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetSetContains("key", null)); Assert.Equal("value", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetContains_ReturnsFalse_WhenGivenSetDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetSetContains("non-existing-set", "some-value"); Assert.False(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetContains_ReturnsTrue_WhenGivenSetExists_AndContainsTheValue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@Key, @Value, 0.0)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new List { new { Key = "my-set", Value = "1" }, new { Key = "my-set", Value = "2" }, }); // Act var result = connection.GetSetContains("my-set", "2"); // Assert Assert.True(result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetContains_ReturnsFalse_WhenGivenSetExists_ButContainsOtherValues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@Key, @Value, 0.0)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new List { new { Key = "my-set", Value = "1" }, new { Key = "my-set", Value = "2" }, }); // Act var result = connection.GetSetContains("my-set", "3"); // Assert Assert.False(result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetContains_ReturnsFalse_WhenAnotherSetContainsTheValue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@Key, @Value, 0.0)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new List { new { Key = "my-set", Value = "1" }, new { Key = "another-set", Value = "2" }, }); // Act var result = connection.GetSetContains("my-set", "2"); // Assert Assert.False(result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetCounter_ReturnsZero_WhenKeyDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetCounter("my-counter"); Assert.Equal(0, result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetCounter_ReturnsSumOfValues_InCounterTable(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Counter ([Key], [Value]) values (@key, @value)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "counter-1", value = 1 }, new { key = "counter-2", value = 1 }, new { key = "counter-1", value = 1 } }); // Act var result = connection.GetCounter("counter-1"); // Assert Assert.Equal(2, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetCounter_IncludesValues_FromCounterAggregateTable(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].AggregatedCounter ([Key], [Value]) values (@key, @value)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "counter-1", value = 12 }, new { key = "counter-2", value = 15 } }); // Act var result = connection.GetCounter("counter-1"); Assert.Equal(12, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetHashCount_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws(() => connection.GetHashCount(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetHashCount_ReturnsZero_WhenKeyDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetHashCount("my-hash"); Assert.Equal(0, result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetHashCount_ReturnsNumber_OfHashFields(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Hash ([Key], [Field]) values (@key, @field)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "hash-1", field = "field-1" }, new { key = "hash-1", field = "field-2" }, new { key = "hash-2", field = "field-1" } }); // Act var result = connection.GetHashCount("hash-1"); // Assert Assert.Equal(2, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetHashTtl_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws( () => connection.GetHashTtl(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetHashTtl_ReturnsNegativeValue_WhenHashDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetHashTtl("my-hash"); Assert.True(result < TimeSpan.Zero); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetHashTtl_ReturnsExpirationTimeForHash(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Hash ([Key], [Field], [ExpireAt]) values (@key, @field, @expireAt)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "hash-1", field = "field", expireAt = (DateTime?)DateTime.UtcNow.AddHours(1) }, new { key = "hash-2", field = "field", expireAt = (DateTime?) null } }); // Act var result = connection.GetHashTtl("hash-1"); // Assert Assert.True(TimeSpan.FromMinutes(59) < result); Assert.True(result < TimeSpan.FromMinutes(61)); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetListCount_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws( () => connection.GetListCount(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetListCount_ReturnsZero_WhenListDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetListCount("my-list"); Assert.Equal(0, result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetListCount_ReturnsTheNumberOfListElements(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].List ([Key]) values (@key)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "list-1" }, new { key = "list-1" }, new { key = "list-2" } }); // Act var result = connection.GetListCount("list-1"); // Assert Assert.Equal(2, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetListTtl_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws( () => connection.GetListTtl(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetListTtl_ReturnsNegativeValue_WhenListDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetListTtl("my-list"); Assert.True(result < TimeSpan.Zero); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetListTtl_ReturnsExpirationTimeForList(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].List ([Key], [ExpireAt]) values (@key, @expireAt)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "list-1", expireAt = (DateTime?) DateTime.UtcNow.AddHours(1) }, new { key = "list-2", expireAt = (DateTime?) null } }); // Act var result = connection.GetListTtl("list-1"); // Assert Assert.True(TimeSpan.FromMinutes(59) < result); Assert.True(result < TimeSpan.FromMinutes(61)); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetValueFromHash_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetValueFromHash(null, "name")); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetValueFromHash_ThrowsAnException_WhenNameIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetValueFromHash("key", null)); Assert.Equal("name", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetValueFromHash_ReturnsNull_WhenHashDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetValueFromHash("my-hash", "name"); Assert.Null(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetValueFromHash_ReturnsValue_OfAGivenField(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Hash ([Key], [Field], [Value]) values (@key, @field, @value)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "hash-1", field = "field-1", value = "1" }, new { key = "hash-1", field = "field-2", value = "2" }, new { key = "hash-2", field = "field-1", value = "3" } }); // Act var result = connection.GetValueFromHash("hash-1", "field-1"); // Assert Assert.Equal("1", result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetRangeFromList_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var exception = Assert.Throws( () => connection.GetRangeFromList(null, 0, 1)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetRangeFromList_ReturnsAnEmptyList_WhenListDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetRangeFromList("my-list", 0, 1); Assert.Empty(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetRangeFromList_ReturnsAllEntries_WithinGivenBounds(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].List ([Key], [Value]) values (@key, @value)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "list-1", value = "1" }, new { key = "list-2", value = "2" }, new { key = "list-1", value = "3" }, new { key = "list-1", value = "4" }, new { key = "list-1", value = "5" } }); // Act var result = connection.GetRangeFromList("list-1", 1, 2); // Assert Assert.Equal(new [] { "4", "3" }, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllItemsFromList_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws( () => connection.GetAllItemsFromList(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllItemsFromList_ReturnsAnEmptyList_WhenListDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetAllItemsFromList("my-list"); Assert.Empty(result); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetAllItemsFromList_ReturnsAllItems_FromAGivenList(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].List ([Key], Value) values (@key, @value)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "list-1", value = "1" }, new { key = "list-2", value = "2" }, new { key = "list-1", value = "3" } }); // Act var result = connection.GetAllItemsFromList("list-1"); // Assert Assert.Equal(new [] { "3", "1" }, result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetTtl_ThrowsAnException_WhenKeyIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { Assert.Throws(() => connection.GetSetTtl(null)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetTtl_ReturnsNegativeValue_WhenSetDoesNotExist(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var result = connection.GetSetTtl("my-set"); Assert.True(result < TimeSpan.Zero); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetSetTtl_ReturnsExpirationTime_OfAGivenSet(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [ExpireAt], [Score]) values (@key, @value, @expireAt, 0.0)"; UseConnections((sql, connection) => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "set-1", value = "1", expireAt = (DateTime?) DateTime.UtcNow.AddMinutes(60) }, new { key = "set-2", value = "2", expireAt = (DateTime?) null } }); // Act var result = connection.GetSetTtl("set-1"); // Assert Assert.True(TimeSpan.FromMinutes(59) < result); Assert.True(result < TimeSpan.FromMinutes(61)); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobData_ReturnsResult_WhenJobIdIsLongValue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON; insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, StateName, CreatedAt) values (@jobId, @invocationData, '[''Arguments'']', 'Succeeded', getutcdate());"; UseConnections((sql, connection) => { var job = Job.FromExpression(() => SampleMethod("hello")); sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L, invocationData = JobHelper.ToJson(InvocationData.Serialize(job)), }); var result = connection.GetJobData((int.MaxValue + 1L).ToString()); Assert.NotNull(result); Assert.NotNull(result.Job); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetStateData_ReturnsCorrectData_WhenJobIdAndStateIdAreLongValues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, StateName, CreatedAt) values (@jobId, '', '', '', getutcdate()); insert into [{Constants.DefaultSchema}].State (JobId, Name, CreatedAt) values (@jobId, 'old-state', getutcdate()); SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job OFF SET IDENTITY_INSERT [{Constants.DefaultSchema}].State ON insert into [{Constants.DefaultSchema}].State (Id, JobId, Name, Data, CreatedAt) values (@stateId, @jobId, 'Name', @data, getutcdate()); update [{Constants.DefaultSchema}].Job set StateId = @stateId;"; UseConnections((sql, connection) => { var data = new Dictionary { { "Key", "Value" } }; sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L, stateId = int.MaxValue + 1L, data = JobHelper.ToJson(data) }); var result = connection.GetStateData((int.MaxValue + 1L).ToString()); Assert.NotNull(result); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void CreateExpiredJob_HandlesJobIdCanExceedInt32Max(bool useMicrosoftDataSqlClient) { UseConnections((sql, connection) => { // Arrange sql.Query($"DBCC CHECKIDENT('[{Constants.DefaultSchema}].Job', RESEED, {int.MaxValue + 1L});"); // Act var createdAt = new DateTime(2012, 12, 12); var jobId = connection.CreateExpiredJob( Job.FromExpression(() => SampleMethod("Hello")), new Dictionary(), createdAt, TimeSpan.FromDays(1)); // Assert Assert.True(int.MaxValue < long.Parse(jobId)); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void SetJobParameter_CreatesNewParameter_WhenJobIdIsLongValue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, CreatedAt) values (@jobId, '', '', getutcdate())"; UseConnections((sql, connection) => { sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L}); connection.SetJobParameter((int.MaxValue + 1L).ToString(), "Name", "Value"); var parameter = sql.Query( $"select * from [{Constants.DefaultSchema}].JobParameter where JobId = @id and Name = @name", new { id = int.MaxValue + 1L, name = "Name" }).Single(); Assert.NotNull(parameter); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetJobParameter_ReturnsParameterValue_WhenJobIdIsLong(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, CreatedAt) values (@jobId, '', '', getutcdate()) SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job OFF insert into [{Constants.DefaultSchema}].JobParameter (JobId, Name, Value) values (@jobId, @name, @value)"; UseConnections((sql, connection) => { sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L, name = "name", value = "value" }); var value = connection.GetJobParameter((int.MaxValue + 1L).ToString(), "name"); Assert.Equal("value", value); }, useBatching: false, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void GetUtcDateTime_ReturnsCurrentUtcDateTime(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var dateTime = connection.GetUtcDateTime(); var currentDateTime = DateTime.UtcNow; Assert.Equal(DateTimeKind.Utc, dateTime.Kind); Assert.True(currentDateTime.AddMinutes(-1) < dateTime, dateTime.ToString(CultureInfo.CurrentCulture)); Assert.True(dateTime < currentDateTime.AddMinutes(1), dateTime.ToString(CultureInfo.CurrentCulture)); }, useMicrosoftDataSqlClient); } [Fact, CleanSerializerSettings] public void HandlesChangingProcessOfStateDataSerialization() { GlobalConfiguration.Configuration.UseSerializerSettings(SerializerSettingsHelper.DangerousSettings); var stateData = new Dictionary { { "key1", "value1" }, { "key2", null } }; var serializedData = SerializationHelper.Serialize(stateData, SerializationOption.User); var deserializedStateData = SerializationHelper.Deserialize>(serializedData); Assert.NotNull(deserializedStateData); Assert.Equal(2, deserializedStateData.Count); Assert.Equal("value1", deserializedStateData["key1"]); Assert.Null(deserializedStateData["key2"]); } [Fact, CleanSerializerSettings] public void HandlesChangingProcessOfInvocationDataSerialization() { GlobalConfiguration.Configuration.UseSerializerSettings(SerializerSettingsHelper.DangerousSettings); var initialJob = Job.FromExpression(() => Console.WriteLine()); var invocationData = InvocationData.Serialize(initialJob); var serializedInvocationData = SerializationHelper.Serialize(invocationData, SerializationOption.User); var deserializedStateData = SerializationHelper.Deserialize(serializedInvocationData); var deserializedJob = deserializedStateData.Deserialize(); Assert.Equal(initialJob.Args, deserializedJob.Args); Assert.Equal(initialJob.Method, deserializedJob.Method); Assert.Equal(initialJob.Type, deserializedJob.Type); } private void UseConnections(Action action, bool useBatching, bool useMicrosoftDataSqlClient) { using (var sqlConnection = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { var storage = new SqlServerStorage( () => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient), new SqlServerStorageOptions { CommandBatchMaxTimeout = useBatching ? TimeSpan.FromMinutes(1) : (TimeSpan?)null }); using (var connection = new SqlServerConnection(storage)) { action(sqlConnection, connection); } } } private void UseConnection(Action action, bool useMicrosoftDataSqlClient) { var storage = new Mock((Func)(() => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient))); storage.Setup(x => x.QueueProviders).Returns(_providers); using (var connection = new SqlServerConnection(storage.Object)) { action(connection); } } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void SampleMethod(string arg){ } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerDistributedLockFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Data.Common; using System.Linq; using System.Reflection; using System.Threading; using ReferencedDapper::Dapper; using Hangfire.Storage; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Tests { public class SqlServerDistributedLockFacts { private readonly TimeSpan _timeout = TimeSpan.FromSeconds(5); [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new SqlServerDistributedLock(null, "hello", _timeout)); Assert.Equal("storage", exception.ParamName); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Ctor_ThrowsAnException_WhenResourceIsNullOrEmpty(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var storage = CreateStorage(connection); var exception = Assert.Throws( () => new SqlServerDistributedLock(storage, "", _timeout)); Assert.Equal("resource", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Ctor_AcquiresExclusiveApplicationLock_OnSession(bool useMicrosoftDataSqlClient) { UseConnection(sql => { // ReSharper disable once UnusedVariable var storage = CreateStorage(sql); using (new SqlServerDistributedLock(storage, "hello", _timeout)) { var lockMode = sql.Query( "select applock_mode('public', 'hello', 'session')").Single(); Assert.Equal("Exclusive", lockMode); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Ctor_ThrowsAnException_IfLockCanNotBeGranted(bool useMicrosoftDataSqlClient) { var releaseLock = new ManualResetEventSlim(false); var lockAcquired = new ManualResetEventSlim(false); var thread = new Thread( () => UseConnection(connection1 => { var storage = CreateStorage(connection1); using (new SqlServerDistributedLock(storage, "exclusive", TimeSpan.Zero)) { lockAcquired.Set(); releaseLock.Wait(); } }, useMicrosoftDataSqlClient)); thread.Start(); lockAcquired.Wait(); UseConnection(connection2 => { var storage = CreateStorage(connection2); Assert.Throws( () => { using (new SqlServerDistributedLock(storage, "exclusive", TimeSpan.FromSeconds(1))) { } }); }, useMicrosoftDataSqlClient); releaseLock.Set(); thread.Join(); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dispose_ReleasesExclusiveApplicationLock(bool useMicrosoftDataSqlClient) { UseConnection(sql => { var storage = CreateStorage(sql); var distributedLock = new SqlServerDistributedLock(storage, "hello", _timeout); distributedLock.Dispose(); var lockMode = sql.Query( "select applock_mode('public', 'hello', 'session')").Single(); Assert.Equal("NoLock", lockMode); }, useMicrosoftDataSqlClient); } [Fact, CleanDatabase] public void DistributedLocks_AreReEntrant_FromTheSameThread_OnTheSameResource() { var storage = new SqlServerStorage(ConnectionUtils.GetConnectionString()); using (new SqlServerDistributedLock(storage, "hello", TimeSpan.FromMinutes(5))) using (new SqlServerDistributedLock(storage, "hello", TimeSpan.FromMinutes(5))) { Assert.True(true); } } [Fact, CleanDatabase] public void InnerDistributedLock_DoesNotConsumeADatabaseConnection() { // Arrange var storage = new SqlServerStorage(ConnectionUtils.GetConnectionString()); // Act using (var outer = new SqlServerDistributedLock(storage, "hello", TimeSpan.FromMinutes(5))) using (var inner = new SqlServerDistributedLock(storage, "hello", TimeSpan.FromMinutes(5))) { // Assert var field = typeof(SqlServerDistributedLock).GetField("_connection", BindingFlags.Instance | BindingFlags.NonPublic); Assert.NotNull(field); Assert.NotNull(field.GetValue(outer)); Assert.Null(field.GetValue(inner)); } } private static SqlServerStorage CreateStorage(DbConnection connection) { return new SqlServerStorage(connection); } private static void UseConnection(Action action, bool useMicrosoftDataSqlClient) { using (var connection = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { action(connection); } } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerJobQueueFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Data.Common; using System.Linq; using System.Threading; using Hangfire.Annotations; using ReferencedDapper::Dapper; using Xunit; // ReSharper disable ArgumentsStyleLiteral // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Tests { public class SqlServerJobQueueFacts { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(5); private static readonly string[] DefaultQueues = { "default" }; [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new SqlServerJobQueue(null, new SqlServerStorageOptions())); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenOptionsValueIsNull() { var exception = Assert.Throws( () => new SqlServerJobQueue(new SqlServerStorage(ConnectionUtils.GetConnectionString()), null)); Assert.Equal("options", exception.ParamName); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); var exception = Assert.Throws( () => queue.Dequeue(null, CreateTimingOutCancellationToken())); Assert.Equal("queues", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldThrowAnException_WhenQueuesCollectionIsEmpty(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); var exception = Assert.Throws( () => queue.Dequeue(new string[0], CreateTimingOutCancellationToken())); Assert.Equal("queues", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var cts = new CancellationTokenSource(); cts.Cancel(); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); Assert.Throws( () => queue.Dequeue(DefaultQueues, cts.Token)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var cts = new CancellationTokenSource(200); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); Assert.Throws( () => queue.Dequeue(DefaultQueues, cts.Token)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (@jobId, @queue); select scope_identity() as Id;"; // Arrange UseConnection(connection => { // ReSharper disable once UnusedVariable var id = (int)connection.Query( arrangeSql, new { jobId = 1, queue = "default" }).Single().Id; var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); // Act using (var payload = (SqlServerTransactionJob) queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken())) { // Assert Assert.Equal("1", payload.JobId); Assert.Equal("default", payload.Queue); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_FetchesAJob_WhenJobIdIsLongValue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (@jobId, @queue); select scope_identity() as Id;"; // Arrange UseConnection(connection => { // ReSharper disable once UnusedVariable var id = (int)connection.Query( arrangeSql, new { jobId = int.MaxValue + 1L, queue = "default" }).Single().Id; var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); // Act using (var payload = (SqlServerTransactionJob) queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken())) { // Assert Assert.Equal((int.MaxValue + 1L).ToString(), payload.JobId); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldDeleteAJob(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; // Arrange UseConnection(connection => { connection.Execute( arrangeSql, new { invocationData = "", arguments = "", queue = "default" }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); // Act using (var payload = queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken())) { // Assert Assert.NotNull(payload); UseConnection(connection2 => { var jobInQueue = connection2.Query($"select * from [{Constants.DefaultSchema}].JobQueue with (readpast)").SingleOrDefault(); Assert.Null(jobInQueue); }, useMicrosoftDataSqlClient); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldFetchTimedOutJobs_FromTheSpecifiedQueue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, dateadd(minute, -60, getutcdate())) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue, FetchedAt) values (scope_identity(), @queue, @fetchedAt)"; // Arrange UseConnection(connection => { connection.Execute( arrangeSql, new { queue = "default", fetchedAt = DateTime.UtcNow.AddDays(-1), invocationData = "", arguments = "" }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); // Act using (var payload = queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken())) { // Assert Assert.NotEmpty(payload.JobId); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldSetFetchedAt_OnlyForTheFetchedJob(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; // Arrange UseConnection(connection => { connection.Execute( arrangeSql, new[] { new { queue = "default", invocationData = "", arguments = "" }, new { queue = "default", invocationData = "", arguments = "" } }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); // Act using (var payload = queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken())) { // Assert UseConnection(connection2 => { var otherJobFetchedAt = connection2.Query( $"select FetchedAt from [{Constants.DefaultSchema}].JobQueue with (readpast) where JobId != @id", new { id = payload.JobId }).Single(); Assert.Null(otherJobFetchedAt); }, useMicrosoftDataSqlClient); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); connection.Execute( arrangeSql, new { queue = "critical", invocationData = "", arguments = "" }); Assert.Throws( () => queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken())); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_ShouldFetchJobs_FromMultipleQueues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; UseConnection(connection => { connection.Execute( arrangeSql, new[] { new { queue = "default", invocationData = "", arguments = "" }, new { queue = "critical", invocationData = "", arguments = "" } }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); using (var critical = (SqlServerTransactionJob) queue.Dequeue( new[] { "critical", "default" }, CreateTimingOutCancellationToken())) { Assert.NotNull(critical.JobId); Assert.Equal("critical", critical.Queue); critical.RemoveFromQueue(); } using (var @default = (SqlServerTransactionJob) queue.Dequeue( new[] { "critical", "default" }, CreateTimingOutCancellationToken())) { Assert.NotNull(@default.JobId); Assert.Equal("default", @default.Queue); @default.RemoveFromQueue(); } }, useMicrosoftDataSqlClient); } //--- [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldThrowAnException_WhenQueuesCollectionIsNull(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); var exception = Assert.Throws( () => queue.Dequeue(null, CreateTimingOutCancellationToken())); Assert.Equal("queues", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldThrowAnException_WhenQueuesCollectionIsEmpty(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); var exception = Assert.Throws( () => queue.Dequeue(new string[0], CreateTimingOutCancellationToken())); Assert.Equal("queues", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var cts = new CancellationTokenSource(); cts.Cancel(); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); Assert.Throws( () => queue.Dequeue(DefaultQueues, cts.Token)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldWaitIndefinitely_WhenThereAreNoJobs(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var cts = new CancellationTokenSource(200); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); Assert.Throws( () => queue.Dequeue(DefaultQueues, cts.Token)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldFetchAJob_FromTheSpecifiedQueue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (@jobId, @queue); select scope_identity() as Id;"; // Arrange UseConnection(connection => { var id = (int)connection.Query( arrangeSql, new { jobId = 1, queue = "default" }).Single().Id; var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); // Act var payload = (SqlServerTimeoutJob)queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken()); // Assert Assert.Equal(id, payload.Id); Assert.Equal("1", payload.JobId); Assert.Equal("default", payload.Queue); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldFetchTheFirstJob_FromTheSpecifiedQueue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) output inserted.Id values (@jobId1, @queue), (@jobId2, @queue);"; // Arrange UseConnection(connection => { var id = (int)connection.Query( arrangeSql, new { jobId1 = 1, jobId2 = 2, queue = "default" }).First().Id; var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); // Act var payload = (SqlServerTimeoutJob)queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken()); // Assert Assert.Equal(id, payload.Id); Assert.Equal("1", payload.JobId); Assert.Equal("default", payload.Queue); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldLeaveJobInTheQueue_ButSetItsFetchedAtValue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; // Arrange UseConnection(connection => { connection.Execute( arrangeSql, new { invocationData = "", arguments = "", queue = "default" }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); // Act var payload = queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken()); // Assert Assert.NotNull(payload); var fetchedAt = connection.Query( $"select FetchedAt from [{Constants.DefaultSchema}].JobQueue where JobId = @id", new { id = payload.JobId }).Single(); Assert.NotNull(fetchedAt); Assert.True(fetchedAt > DateTime.UtcNow.AddMinutes(-1)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldFetchATimedOutJobs_FromTheSpecifiedQueue(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue, FetchedAt) values (scope_identity(), @queue, @fetchedAt)"; // Arrange UseConnection(connection => { connection.Execute( arrangeSql, new { queue = "default", fetchedAt = DateTime.UtcNow.AddDays(-1), invocationData = "", arguments = "" }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); // Act var payload = queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken()); // Assert Assert.NotEmpty(payload.JobId); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldSetFetchedAt_OnlyForTheFetchedJob(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; // Arrange UseConnection(connection => { connection.Execute( arrangeSql, new[] { new { queue = "default", invocationData = "", arguments = "" }, new { queue = "default", invocationData = "", arguments = "" } }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); // Act var payload = queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken()); // Assert var otherJobFetchedAt = connection.Query( $"select FetchedAt from [{Constants.DefaultSchema}].JobQueue where JobId != @id", new { id = payload.JobId }).Single(); Assert.Null(otherJobFetchedAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldFetchJobs_OnlyFromSpecifiedQueues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); connection.Execute( arrangeSql, new { queue = "critical", invocationData = "", arguments = "" }); Assert.Throws( () => queue.Dequeue( DefaultQueues, CreateTimingOutCancellationToken())); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dequeue_InvisibilityTimeout_ShouldFetchJobs_FromMultipleQueues(bool useMicrosoftDataSqlClient) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values (@invocationData, @arguments, getutcdate()) insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue) values (scope_identity(), @queue)"; UseConnection(connection => { connection.Execute( arrangeSql, new[] { new { queue = "default", invocationData = "", arguments = "" }, new { queue = "critical", invocationData = "", arguments = "" } }); var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: DefaultTimeout); var critical = (SqlServerTimeoutJob)queue.Dequeue( new[] { "critical", "default" }, CreateTimingOutCancellationToken()); Assert.NotNull(critical.JobId); Assert.Equal("critical", critical.Queue); var @default = (SqlServerTimeoutJob)queue.Dequeue( new[] { "critical", "default" }, CreateTimingOutCancellationToken()); Assert.NotNull(@default.JobId); Assert.Equal("default", @default.Queue); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Enqueue_AddsAJobToTheQueue(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); #if NETCOREAPP using (var transaction = connection.BeginTransaction()) { queue.Enqueue(connection, transaction, "default", "1"); transaction.Commit(); } #else queue.Enqueue(connection, "default", "1"); #endif var record = connection.Query($"select * from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Equal("1", record.JobId.ToString()); Assert.Equal("default", record.Queue); Assert.Null(record.FetchedAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Enqueue_ThrowsAnException_WhenTheGivenQueueIsTooLong(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var queueName = "some-really-long-queue-name-that-should-cause-an-exception-to-be-thrown-and-not-ignored"; var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); var exception = Assert.ThrowsAny(() => { #if NETCOREAPP using (var transaction = connection.BeginTransaction()) { queue.Enqueue(connection, transaction, queueName, "1"); transaction.Commit(); } #else queue.Enqueue(connection, queueName, "1"); #endif }); var record = connection.Query($"select * from [{Constants.DefaultSchema}].JobQueue").SingleOrDefault(); Assert.Null(record); Assert.StartsWith("String or binary data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Enqueue_AddsAJob_WhenIdIsLongValue(bool useMicrosoftDataSqlClient) { UseConnection(connection => { var queue = CreateJobQueue(useMicrosoftDataSqlClient, invisibilityTimeout: null); #if NETCOREAPP using (var transaction = connection.BeginTransaction()) { queue.Enqueue(connection, transaction, "default", (int.MaxValue + 1L).ToString()); transaction.Commit(); } #else queue.Enqueue(connection, "default", (int.MaxValue + 1L).ToString()); #endif var record = connection.Query($"select * from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Equal((int.MaxValue + 1L).ToString(), record.JobId.ToString()); }, useMicrosoftDataSqlClient); } private static CancellationToken CreateTimingOutCancellationToken() { var source = new CancellationTokenSource(TimeSpan.FromSeconds(1)); return source.Token; } private static SqlServerJobQueue CreateJobQueue(bool useMicrosoftDataSqlClient, TimeSpan? invisibilityTimeout) { var storage = new SqlServerStorage(() => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)); return new SqlServerJobQueue(storage, new SqlServerStorageOptions { SlidingInvisibilityTimeout = invisibilityTimeout }); } private static void UseConnection([InstantHandle] Action action, bool useMicrosoftDataSqlClient) { using (var connection = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { action(connection); } } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerMonitoringApiFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Collections.Generic; using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using ReferencedDapper::Dapper; using Hangfire.Common; using Hangfire.Server; using Hangfire.SqlServer.Entities; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable UnusedVariable // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Tests { public class SqlServerMonitoringApiFacts { private readonly DateTime _utcNow = DateTime.UtcNow; [Fact, CleanSerializerSettings] public void HandlesChangingProcessOfServerDataSerialization() { GlobalConfiguration.Configuration.UseSerializerSettings(SerializerSettingsHelper.DangerousSettings); var serverData = new ServerData { WorkerCount = 5, Queues = new[] { "default", "critical" }, StartedAt = new DateTime(2016, 12, 01, 14, 33, 00) }; var serializedServerData = SerializationHelper.Serialize(serverData, SerializationOption.User); var deserializedServerData = SerializationHelper.Deserialize(serializedServerData); Assert.Equal(5, deserializedServerData.WorkerCount); Assert.Equal(new[] { "default", "critical" }, deserializedServerData.Queues); Assert.Equal(new DateTime(2016, 12, 01, 14, 33, 00), deserializedServerData.StartedAt); } [Fact, CleanDatabase] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new SqlServerMonitoringApi(null, null)); Assert.Equal("storage", exception.ParamName); } [Fact, CleanDatabase] public void Queues_ReturnsEmptyCollection_WhenThereAreNoQueues() { var monitoring = CreateMonitoringApi(); var result = monitoring.Queues(); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void Queues_ReturnsCorrectJobs_WhichWillBeDequeuedNext() { // Arrange var jobId = SimpleEnqueueJob( "critical", job: Job.FromExpression(() => Empty()), state: new EnqueuedState()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues(); // Assert var criticalQueue = result.Single(); var queuedJob = criticalQueue.FirstJobs.Single(); Assert.Equal("critical", criticalQueue.Name); Assert.Equal(1, criticalQueue.Length); Assert.Equal(jobId, queuedJob.Key); Assert.True(queuedJob.Value.InEnqueuedState); Assert.Equal("Enqueued", queuedJob.Value.State, StringComparer.OrdinalIgnoreCase); AssertWithinSecond(_utcNow, queuedJob.Value.EnqueuedAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), queuedJob.Value.Job.Type); Assert.Equal("Empty", queuedJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void Queues_IsAbleToHandle_JobIdWithoutCorrespondingBackgroundJobEntry() { // Arrange SimpleEnqueueJob("default", jobId: "41423", noState: true); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues(); // Assert var someJob = result.Single().FirstJobs.Single(); Assert.Equal("41423", someJob.Key); Assert.Null(someJob.Value); } [Fact, CleanDatabase] public void Queues_IsAbleToHandle_BackgroundJobEntry_WithNullState() { // Arrange var jobId = SimpleEnqueueJob("test", noState: true); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues(); // Assert var someJob = result.Single().FirstJobs.Single(); Assert.Equal(jobId, someJob.Key); Assert.False(someJob.Value.InEnqueuedState); Assert.Null(someJob.Value.State); Assert.Null(someJob.Value.EnqueuedAt); } [Fact, CleanDatabase] public void Queues_IsAbleToHandle_BackgroundJobEntry_WithAnotherState() { // Arrange var jobId = SimpleEnqueueJob("default", state: new DeletedState()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues(); // Assert var someJob = result.Single().FirstJobs.Single(); Assert.Equal(jobId, someJob.Key); Assert.False(someJob.Value.InEnqueuedState); Assert.Equal("Deleted", someJob.Value.State, StringComparer.OrdinalIgnoreCase); Assert.Null(someJob.Value.EnqueuedAt); } [Fact, CleanDatabase] public void Queues_IsAbleToHandle_EnqueuedLikeStates_AsEnqueued() { // Arrange var state = new Mock(); state.SetupGet(x => x.Name).Returns("EnQUEued"); var jobId = SimpleEnqueueJob("default", state: state.Object); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues(); // Assert var someJob = result.Single().FirstJobs.Single(); Assert.Equal(jobId, someJob.Key); Assert.True(someJob.Value.InEnqueuedState); Assert.Equal("EnQUEued", someJob.Value.State); AssertWithinSecond(_utcNow, someJob.Value.EnqueuedAt); } [Fact, CleanDatabase] public void Queues_ReturnsTop5Jobs_FromItsHead() { // Arrange var jobId1 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId2 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId3 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId4 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId5 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId6 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId7 = SimpleEnqueueJob("critical", state: new EnqueuedState()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues(); // Assert var defaultQueue = result.Single(x => x.Name == "default"); Assert.Equal(6, defaultQueue.Length); Assert.Equal(5, defaultQueue.FirstJobs.Count); Assert.Equal(jobId1, defaultQueue.FirstJobs.First().Key); Assert.Equal(jobId5, defaultQueue.FirstJobs.Last().Key); var criticalQueue = result.Single(x => x.Name == "critical"); Assert.Equal(1, criticalQueue.Length); Assert.Single(criticalQueue.FirstJobs); Assert.Equal(jobId7, criticalQueue.FirstJobs.Single().Key); } [Fact, CleanDatabase] public void Queues_IsAbleToHandleSerializationProblems_InJobs() { // Arrange var jobId = SimpleEnqueueJob("default", state: new EnqueuedState()); UseSqlConnection(connection => { var wrongData = new InvocationData("asfasf", "232", "afasf", "gg"); var payload = wrongData.SerializePayload(excludeArguments: true); connection.Execute( $"update [{Constants.DefaultSchema}].Job set InvocationData = @data, Arguments = @args where Id = @jobId", new { jobId, data = payload, args = wrongData.Arguments }); }); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues(); // Assert var queuedJob = result.Single().FirstJobs.Single(); Assert.Equal(jobId, queuedJob.Key); Assert.Null(queuedJob.Value.Job); Assert.True(queuedJob.Value.InEnqueuedState); Assert.Equal("Enqueued", queuedJob.Value.State, StringComparer.OrdinalIgnoreCase); } [Fact, CleanDatabase] public void Queues_ProducesSortedList_RegardlessOfActualEnqueueOrder() { // Arrange SimpleEnqueueJob("b-queue", state: new EnqueuedState()); SimpleEnqueueJob("a-queue", state: new EnqueuedState()); SimpleEnqueueJob("c-queue", state: new EnqueuedState()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Queues().ToArray(); // Assert Assert.Equal("a-queue", result[0].Name); Assert.Equal("b-queue", result[1].Name); Assert.Equal("c-queue", result[2].Name); } [Fact, CleanDatabase] public void Servers_ReturnsEmptyCollection_WhenThereAreNoServers() { var monitoring = CreateMonitoringApi(); var result = monitoring.Servers(); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void Servers_ReturnsAllTheRegisteredServers_WithCorrectDetails() { // Arrange UseConnection(connection => { connection.AnnounceServer("server1", new ServerContext { Queues = new []{ "default" }, WorkerCount = 100 }); connection.AnnounceServer("server2", new ServerContext { Queues = new[] { "critical" }, WorkerCount = 17 }); connection.AnnounceServer("server3", new ServerContext { Queues = new[] { "alpha", "beta" }, WorkerCount = 0 }); return true; }); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Servers(); // Assert Assert.Equal(3, result.Count); var server1 = result.Single(x => x.Name == "server1"); Assert.Equal(new [] { "default" }, server1.Queues); Assert.Equal(100, server1.WorkersCount); AssertWithinSecond(_utcNow, server1.StartedAt); AssertWithinSecond(_utcNow, server1.Heartbeat); var server2 = result.Single(x => x.Name == "server2"); Assert.Equal(new[] { "critical" }, server2.Queues); Assert.Equal(17, server2.WorkersCount); AssertWithinSecond(_utcNow, server2.StartedAt); AssertWithinSecond(_utcNow, server2.Heartbeat); var server3 = result.Single(x => x.Name == "server3"); Assert.Equal(new[] { "alpha", "beta" }, server3.Queues); Assert.Equal(0, server3.WorkersCount); AssertWithinSecond(_utcNow, server3.StartedAt); AssertWithinSecond(_utcNow, server3.Heartbeat); } [Fact, CleanDatabase] public void Servers_ProducesSortedList_RegardlessOfActualAnnouncementOrder() { // Arrange UseConnection(connection => { connection.AnnounceServer("server3", new ServerContext { Queues = new[] { "default" }, WorkerCount = 4 }); connection.AnnounceServer("server1", new ServerContext { Queues = new[] { "default" }, WorkerCount = 4 }); connection.AnnounceServer("server2", new ServerContext { Queues = new[] { "default" }, WorkerCount = 4 }); return true; }); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.Servers().ToArray(); // Assert Assert.Equal("server1", result[0].Name); Assert.Equal("server2", result[1].Name); Assert.Equal("server3", result[2].Name); } [Fact, CleanDatabase] public void JobDetails_ThrowsAnException_WhenJobIdIsNull() { var monitoring = CreateMonitoringApi(); var exception = Assert.Throws( () => monitoring.JobDetails(null)); Assert.Equal("jobId", exception.ParamName); } [Fact, CleanDatabase] public void JobDetails_ReturnsNull_WhenTargetJobDoesNotExist() { var monitoring = CreateMonitoringApi(); var result = monitoring.JobDetails("1412"); Assert.Null(result); } [Fact, CleanDatabase] public void JobDetails_CorrectlyHandles_CreatedButNotInitializedJob() { // Arrange var jobId = UseConnection(connection => connection.CreateExpiredJob( Job.FromExpression(() => Empty()), new Dictionary { { "CurrentCulture", "en-US" }, { "RetryCount", "5" } }, _utcNow, TimeSpan.FromMinutes(37))); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.JobDetails(jobId); // Assert Assert.NotNull(result); Assert.Equal(typeof(SqlServerMonitoringApiFacts), result.Job.Type); Assert.Equal("Empty", result.Job.Method.Name); Assert.Equal("en-US", result.Properties["CurrentCulture"]); Assert.Equal("5", result.Properties["RetryCount"]); AssertWithinSecond(_utcNow, result.CreatedAt); AssertWithinSecond(_utcNow.AddMinutes(37), result.ExpireAt); } [Fact, CleanDatabase] public void JobDetails_CorrectlyShowsCreated_AndInitializedJob() { // Arrange var createdId = UseConnection(connection => { var jobId = connection.CreateExpiredJob( Job.FromExpression(() => Empty()), new Dictionary(), _utcNow, TimeSpan.FromMinutes(1)); using (var transaction = connection.CreateWriteTransaction()) { transaction.SetJobState(jobId, new EnqueuedState("critical") { Reason = "Some reason" }); transaction.Commit(); } using (var transaction = connection.CreateWriteTransaction()) { transaction.SetJobState(jobId, new DeletedState()); transaction.Commit(); } return jobId; }); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.JobDetails(createdId); // Assert Assert.NotNull(result); Assert.Equal("Deleted", result.History.First().StateName); Assert.Null(result.History.First().Reason); AssertWithinSecond(_utcNow, result.History.First().CreatedAt); Assert.Equal("Enqueued", result.History.Last().StateName, StringComparer.OrdinalIgnoreCase); Assert.Equal("Some reason", result.History.Last().Reason); Assert.Equal("critical", result.History.Last().Data["Queue"]); AssertWithinSecond(_utcNow.Add(TimeSpan.FromMinutes(-1)), result.History.Last().CreatedAt); } [Fact, CleanDatabase] public void JobDetails_IsAbleToHandleSerializationProblems() { // Arrange var jobId = UseConnection(connection => connection.CreateExpiredJob( Job.FromExpression(() => Empty()), new Dictionary(), _utcNow, TimeSpan.FromMinutes(1))); UseSqlConnection(connection => { var wrongData = new InvocationData("asfasf", "232", "afasf", "gg"); var payload = wrongData.SerializePayload(excludeArguments: true); connection.Execute( $"update [{Constants.DefaultSchema}].Job set InvocationData = @data, Arguments = @args where Id = @jobId", new { jobId, data = payload, args = wrongData.Arguments }); }); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.JobDetails(jobId); // Assert Assert.NotNull(result); Assert.Null(result.Job); Assert.All(result.Properties, pair => Assert.StartsWith("DBG_", pair.Key)); Assert.Empty(result.History); AssertWithinSecond(_utcNow, result.CreatedAt); AssertWithinSecond(_utcNow, result.ExpireAt); } [Fact, CleanDatabase] public void GetStatistics_ReturnsEmptyStatistics_WhenNothingIsCreatedYet() { var monitoring = CreateMonitoringApi(); var result = monitoring.GetStatistics(); Assert.Equal(0, result.Deleted); Assert.Equal(0, result.Enqueued); Assert.Equal(0, result.Failed); Assert.Equal(0, result.Processing); Assert.Equal(0, result.Queues); Assert.Equal(0, result.Recurring); Assert.Equal(0, result.Scheduled); Assert.Equal(0, result.Servers); Assert.Equal(0, result.Succeeded); #if !HANGFIRE_170 Assert.Equal(0, result.Retries); #endif } [Fact, CleanDatabase] public void EnqueuedJobs_ThrowsAnException_WhenQueueNamesIsNull() { var monitoring = CreateMonitoringApi(); var exception = Assert.Throws( () => monitoring.EnqueuedJobs(null, 0, 10)); Assert.Equal("queue", exception.ParamName); } [Fact, CleanDatabase] public void EnqueuedJobs_ReturnsEmptyCollection_WhenThereIsNoSuchQueue() { var monitoring = CreateMonitoringApi(); var result = monitoring.EnqueuedJobs("critical", 0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void EnqueuedJobs_ReturnsCorrectJobs_WhichWillBeDequeuedNext() { // Arrange var jobId = SimpleEnqueueJob( "critical", job: Job.FromExpression(() => Empty()), state: new EnqueuedState()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("critical", 0, 10); // Assert var queuedJob = result.Single(); Assert.Equal(jobId, queuedJob.Key); Assert.True(queuedJob.Value.InEnqueuedState); Assert.Equal("Enqueued", queuedJob.Value.State, StringComparer.OrdinalIgnoreCase); AssertWithinSecond(_utcNow, queuedJob.Value.EnqueuedAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), queuedJob.Value.Job.Type); Assert.Equal("Empty", queuedJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void EnqueuedJobs_ReturnsJobs_InTheAscendingOrder() { // Arrange var jobId1 = SimpleEnqueueJob("critical"); var jobId2 = SimpleEnqueueJob("critical"); var jobId3 = SimpleEnqueueJob("critical"); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("critical", 0, 10).ToArray(); // Assert Assert.Equal(jobId1, result[0].Key); Assert.Equal(jobId2, result[1].Key); Assert.Equal(jobId3, result[2].Key); } [Fact, CleanDatabase] public void EnqueuedJobs_IsAbleToHandle_JobIdWithoutCorrespondingBackgroundJobEntry() { // Arrange SimpleEnqueueJob("default", jobId: "12345", noState: true); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("default", 0, 10); // Assert var someJob = result.Single(); Assert.Equal("12345", someJob.Key); Assert.Null(someJob.Value); } [Fact, CleanDatabase] public void EnqueuedJobs_IsAbleToHandle_BackgroundJobEntry_WithNullState() { // Arrange var jobId = SimpleEnqueueJob("test", noState: true); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("test", 0, 10); // Assert var someJob = result.Single(); Assert.Equal(jobId, someJob.Key); Assert.False(someJob.Value.InEnqueuedState); Assert.Null(someJob.Value.State); Assert.Null(someJob.Value.EnqueuedAt); } [Fact, CleanDatabase] public void EnqueuedJobs_IsAbleToHandle_MultipleEntriesWithTheSameJobId() { // Arrange var jobId = SimpleEnqueueJob("default"); var sameId = SimpleEnqueueJob("default", jobId: jobId); var monitoring = CreateMonitoringApi(); // Act var enqueued = monitoring.EnqueuedJobs("default", 0, 10).ToArray(); // Assert Assert.Equal(2, enqueued.Length); Assert.All(enqueued, x => Assert.Equal(jobId, x.Key)); Assert.All(enqueued, x => Assert.Equal("Enqueued", x.Value.State)); } [Fact, CleanDatabase] public void EnqueuedJobs_IsAbleToHandle_BackgroundJobEntry_WithAnotherState() { // Arrange var jobId = SimpleEnqueueJob("default", state: new DeletedState()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("default", 0, 10); // Assert var someJob = result.Single(); Assert.Equal(jobId, someJob.Key); Assert.False(someJob.Value.InEnqueuedState); Assert.Equal("Deleted", someJob.Value.State, StringComparer.OrdinalIgnoreCase); Assert.Null(someJob.Value.EnqueuedAt); } [Fact, CleanDatabase] public void EnqueuedJobs_IsAbleToHandle_EnqueuedLikeStates_AsEnqueued() { // Arrange var state = new Mock(); state.SetupGet(x => x.Name).Returns("EnQUEued"); var jobId = SimpleEnqueueJob("default", state: state.Object); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("default", 0, 10); // Assert var someJob = result.Single(); Assert.Equal(jobId, someJob.Key); Assert.True(someJob.Value.InEnqueuedState); Assert.Equal("EnQUEued", someJob.Value.State); AssertWithinSecond(_utcNow, someJob.Value.EnqueuedAt); } [Fact, CleanDatabase] public void EnqueuedJobs_ReturnsJobs_WithinTheGivenRange_FromTheGivenQueue() { // Arrange var jobId1 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId2 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobIdX = SimpleEnqueueJob("critical", state: new EnqueuedState()); var jobId3 = SimpleEnqueueJob("default", state: new EnqueuedState()); var jobId4 = SimpleEnqueueJob("default", state: new EnqueuedState()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("default", 1, 2); // Assert Assert.Equal(2, result.Count); Assert.Equal(jobId2, result.First().Key); Assert.Equal(jobId3, result.Last().Key); } [Fact, CleanDatabase] public void EnqueuedJobs_IsAbleToHandleSerializationProblems_InJobs() { // Arrange var jobId = SimpleEnqueueJob("default", state: new EnqueuedState()); UseSqlConnection(connection => { var wrongData = new InvocationData("asfasf", "232", "afasf", "gg"); var payload = wrongData.SerializePayload(excludeArguments: true); connection.Execute( $"update [{Constants.DefaultSchema}].Job set InvocationData = @data, Arguments = @args where Id = @jobId", new { jobId, data = payload, args = wrongData.Arguments }); }); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedJobs("default", 0, 10); // Assert var queuedJob = result.Single(); Assert.Equal(jobId, queuedJob.Key); Assert.Null(queuedJob.Value.Job); Assert.True(queuedJob.Value.InEnqueuedState); Assert.Equal("Enqueued", queuedJob.Value.State, StringComparer.OrdinalIgnoreCase); } [Fact, CleanDatabase] public void FetchedJobs_ThrowsAnException_WhenQueueIsNull() { var monitoring = CreateMonitoringApi(); var exception = Assert.Throws( () => monitoring.FetchedJobs(null, 0, 10)); Assert.Equal("queue", exception.ParamName); } [Fact, CleanDatabase] public void FetchedJobs_ReturnsEmptyCollection() { var monitoring = CreateMonitoringApi(); var result = monitoring.FetchedJobs("default", 0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void ProcessingJobs_ReturnsEmptyCollection_WhenThereAreNoProcessingJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.ProcessingJobs(0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void ProcessingJobs_ReturnsCorrectJobs_InTheProcessingState() { // Arrange var jobId = SimpleProcessingJob("server-1", "worker-1", job: Job.FromExpression(() => Empty())); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ProcessingJobs(0, 10); // Assert var processingJob = result.Single(); Assert.Equal(jobId, processingJob.Key); Assert.True(processingJob.Value.InProcessingState); Assert.Equal("server-1", processingJob.Value.ServerId); AssertWithinSecond(_utcNow, processingJob.Value.StartedAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), processingJob.Value.Job.Type); Assert.Equal("Empty", processingJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void ProcessingJobs_ReturnsJobs_InTheAscendingOrder() { // Arrange var jobId1 = SimpleProcessingJob(); var jobId2 = SimpleProcessingJob(); var jobId3 = SimpleProcessingJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ProcessingJobs(0, 10).ToArray(); // Assert Assert.Equal(jobId1, result[0].Key); Assert.Equal(jobId2, result[1].Key); Assert.Equal(jobId3, result[2].Key); } [Fact, CleanDatabase] public void ProcessingJobs_ReturnsJobs_WithinTheGivenRange() { // Arrange var jobId1 = SimpleProcessingJob(); var jobId2 = SimpleProcessingJob(); var jobId3 = SimpleProcessingJob(); var jobId4 = SimpleProcessingJob(); var jobId5 = SimpleProcessingJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ProcessingJobs(1, 2).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal(jobId2, result[0].Key); Assert.Equal(jobId3, result[1].Key); } [Fact, CleanDatabase] public void ScheduledJobs_ReturnsEmptyCollection_WhenThereAreNoScheduledJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.ScheduledJobs(0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void ScheduledJobs_ReturnsCorrectJobs_InTheScheduledState_WithNonCompositeIndex() { // Arrange var jobId = SimpleScheduledJob( TimeSpan.FromDays(1), job: Job.FromExpression(() => Empty())); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ScheduledJobs(0, 10); // Assert var scheduledJob = result.Single(); Assert.Equal(jobId, scheduledJob.Key); Assert.True(scheduledJob.Value.InScheduledState); AssertWithinSecond(_utcNow.Add(TimeSpan.FromDays(1)), scheduledJob.Value.EnqueueAt); AssertWithinSecond(_utcNow, scheduledJob.Value.ScheduledAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), scheduledJob.Value.Job.Type); Assert.Equal("Empty", scheduledJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void ScheduledJobs_ReturnsCorrectJobs_InTheScheduledState_WithCompositeIndex() { // Arrange var jobId = SimpleScheduledJob( TimeSpan.FromHours(1), job: Job.FromExpression(() => Empty(), "critical")); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ScheduledJobs(0, 10); // Assert var scheduledJob = result.Single(); Assert.Equal(jobId, scheduledJob.Key); Assert.Equal("critical", scheduledJob.Value.Job.Queue); } [Fact, CleanDatabase] public void ScheduledJobs_ReturnsJobs_InTheAscendingOrder() { // Arrange var jobId1 = SimpleScheduledJob(); var jobId2 = SimpleScheduledJob(); var jobId3 = SimpleScheduledJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ScheduledJobs(0, 10).ToArray(); // Assert Assert.Equal(jobId1, result[0].Key); Assert.Equal(jobId2, result[1].Key); Assert.Equal(jobId3, result[2].Key); } [Fact, CleanDatabase] public void ScheduledJobs_ReturnsJobs_WithinTheGivenRange() { // Arrange var jobId1 = SimpleScheduledJob(); var jobId2 = SimpleScheduledJob(); var jobId3 = SimpleScheduledJob(); var jobId4 = SimpleScheduledJob(); var jobId5 = SimpleScheduledJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ScheduledJobs(1, 2).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal(jobId2, result[0].Key); Assert.Equal(jobId3, result[1].Key); } [Fact, CleanDatabase] public void SucceededJobs_ReturnsEmptyCollection_WhenThereAreNoSucceededJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.SucceededJobs(0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void SucceededJobs_ReturnsCorrectJobs_InTheSucceededState() { // Arrange var jobId = SimpleSucceededJob( "hello", 123, 456, job: Job.FromExpression(() => Empty())); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.SucceededJobs(0, 10); // Assert var succeededJob = result.Single(); Assert.Equal(jobId, succeededJob.Key); Assert.True(succeededJob.Value.InSucceededState); Assert.Equal("\"hello\"", succeededJob.Value.Result); Assert.Equal(123 + 456, succeededJob.Value.TotalDuration); AssertWithinSecond(_utcNow, succeededJob.Value.SucceededAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), succeededJob.Value.Job.Type); Assert.Equal("Empty", succeededJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void SucceededJobs_ReturnsJobs_InTheDescendingOrder() { // Arrange var jobId1 = SimpleSucceededJob(); var jobId2 = SimpleSucceededJob(); var jobId3 = SimpleSucceededJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.SucceededJobs(0, 10).ToArray(); // Assert Assert.Equal(jobId3, result[0].Key); Assert.Equal(jobId2, result[1].Key); Assert.Equal(jobId1, result[2].Key); } [Fact, CleanDatabase] public void SucceededJobs_ReturnsJobs_WithinTheGivenRange() { // Arrange var jobId1 = SimpleSucceededJob(); var jobId2 = SimpleSucceededJob(); var jobId3 = SimpleSucceededJob(); var jobId4 = SimpleSucceededJob(); var jobId5 = SimpleSucceededJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.SucceededJobs(1, 2).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal(jobId4, result[0].Key); Assert.Equal(jobId3, result[1].Key); } [Fact, CleanDatabase] public void SucceededJobs_IsAbleToHandle_ExpiredJobEntry() { // Arrange var jobId1 = SimpleSucceededJob(expireIn: TimeSpan.FromHours(-1)); UseSqlConnection(connection => connection.Execute( $"delete from[{Constants.DefaultSchema}].Job where ExpireAt is not null")); var monitoring = CreateMonitoringApi(); // Act var someJob = monitoring.SucceededJobs(0, 10).SingleOrDefault(); // Assert Assert.True(someJob.Key == null || someJob.Key == jobId1, someJob.Key); Assert.Null(someJob.Value); } [Fact, CleanDatabase] public void FailedJobs_ReturnsEmptyCollection_WhenThereAreNoFailedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.FailedJobs(0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void FailedJobs_ReturnsCorrectJobs_InTheFailedState() { // Arrange var jobId = SimpleJob( job: Job.FromExpression(() => Empty()), state: new FailedState(new InvalidOperationException("Hello, world!")) { Reason = "Some reason" }); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.FailedJobs(0, 10); // Assert var failedJob = result.Single(); Assert.Equal(jobId, failedJob.Key); Assert.True(failedJob.Value.InFailedState); Assert.Equal("Some reason", failedJob.Value.Reason); Assert.Equal(typeof(InvalidOperationException).FullName, failedJob.Value.ExceptionType); Assert.Equal("Hello, world!", failedJob.Value.ExceptionMessage); AssertWithinSecond(_utcNow, failedJob.Value.FailedAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), failedJob.Value.Job.Type); Assert.Equal("Empty", failedJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void FailedJobs_ReturnsJobs_InTheDescendingOrder() { // Arrange var jobId1 = SimpleFailedJob(new InvalidOperationException()); var jobId2 = SimpleFailedJob(new OperationCanceledException()); var jobId3 = SimpleFailedJob(new NotSupportedException()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.FailedJobs(0, 10).ToArray(); // Assert Assert.Equal(jobId3, result[0].Key); Assert.Equal(jobId2, result[1].Key); Assert.Equal(jobId1, result[2].Key); } [Fact, CleanDatabase] public void FailedJobs_ReturnsJobs_WithinTheGivenRange() { // Arrange var jobId1 = SimpleFailedJob(); var jobId2 = SimpleFailedJob(); var jobId3 = SimpleFailedJob(); var jobId4 = SimpleFailedJob(); var jobId5 = SimpleFailedJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.FailedJobs(1, 2).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal(jobId4, result[0].Key); Assert.Equal(jobId3, result[1].Key); } [Fact, CleanDatabase] public void DeletedJobs_ReturnsEmptyCollection_WhenThereAreNoDeletedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.DeletedJobs(0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void DeletedJobs_ReturnsCorrectJobs_InTheDeletedState() { // Arrange var jobId = SimpleDeletedJob( exception: new InvalidOperationException("Hello, world!"), job: Job.FromExpression(() => Empty())); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.DeletedJobs(0, 10); // Assert var deletedJob = result.Single(); Assert.Equal(jobId, deletedJob.Key); Assert.True(deletedJob.Value.InDeletedState); AssertWithinSecond(_utcNow, deletedJob.Value.DeletedAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), deletedJob.Value.Job.Type); Assert.Equal("Empty", deletedJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void DeletedJobs_ReturnsJobs_InTheDescendingOrder() { // Arrange var jobId1 = SimpleDeletedJob(new InvalidOperationException()); var jobId2 = SimpleDeletedJob(new NotSupportedException()); var jobId3 = SimpleDeletedJob(new ArgumentNullException()); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.DeletedJobs(0, 10).ToArray(); // Assert Assert.Equal(jobId3, result[0].Key); Assert.Equal(jobId2, result[1].Key); Assert.Equal(jobId1, result[2].Key); } [Fact, CleanDatabase] public void DeletedJobs_ReturnsJobs_WithinTheGivenRange() { // Arrange var jobId1 = SimpleDeletedJob(); var jobId2 = SimpleDeletedJob(); var jobId3 = SimpleDeletedJob(); var jobId4 = SimpleDeletedJob(); var jobId5 = SimpleDeletedJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.DeletedJobs(1, 2).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal(jobId4, result[0].Key); Assert.Equal(jobId3, result[1].Key); } [Fact, CleanDatabase] public void DeletedJobs_IsAbleToHandle_ExpiredJobEntry() { // Arrange var jobId1 = SimpleDeletedJob(expireIn: TimeSpan.FromHours(-1)); UseSqlConnection(connection => connection.Execute( $"delete from[{Constants.DefaultSchema}].Job where ExpireAt is not null")); var monitoring = CreateMonitoringApi(); // Act var someJob = monitoring.DeletedJobs(0, 10).SingleOrDefault(); // Assert Assert.True(someJob.Key == null || someJob.Key == jobId1, someJob.Key); Assert.Null(someJob.Value); } [Fact, CleanDatabase] public void AwaitingJobs_ReturnsEmptyCollection_WhenThereAreNoAwaitingJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.AwaitingJobs(0, 10); Assert.NotNull(result); Assert.Empty(result); } [Fact, CleanDatabase] public void AwaitingJobs_ReturnsCorrectJobs_InTheAwaitingState() { // Arrange var processingId = SimpleProcessingJob("server-1", "worker-1"); var jobId = SimpleAwaitingJob( processingId, job: Job.FromExpression(() => Empty())); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.AwaitingJobs(0, 10); // Assert var awaitingJob = result.Single(); Assert.Equal(jobId, awaitingJob.Key); Assert.True(awaitingJob.Value.InAwaitingState); Assert.Equal("Processing", awaitingJob.Value.ParentStateName); AssertWithinSecond(_utcNow, awaitingJob.Value.AwaitingAt); Assert.Equal(typeof(SqlServerMonitoringApiFacts), awaitingJob.Value.Job.Type); Assert.Equal("Empty", awaitingJob.Value.Job.Method.Name); } [Fact, CleanDatabase] public void AwaitingJobs_DoesNotFailWhenSeveralJobs_PointToTheSameParentJob() { // Arrange var processingId = SimpleProcessingJob("server-1", "worker-1"); var jobId1 = SimpleAwaitingJob(processingId); var jobId2 = SimpleAwaitingJob(processingId); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.AwaitingJobs(0, 10); // Assert var awaiting = result.ToArray(); Assert.Equal(2, awaiting.Length); Assert.Contains(jobId1, awaiting.Select(x => x.Key)); Assert.Contains(jobId2, awaiting.Select(x => x.Key)); Assert.All(awaiting, x => Assert.Equal("Processing", x.Value.ParentStateName)); } [Fact, CleanDatabase] public void AwaitingJobs_ReturnsJobs_InTheAscendingOrder() { // Arrange var jobId1 = SimpleAwaitingJob("123"); var jobId2 = SimpleAwaitingJob("456"); var jobId3 = SimpleAwaitingJob("789"); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.AwaitingJobs(0, 10).ToArray(); // Assert Assert.Equal(jobId1, result[0].Key); Assert.Equal(jobId2, result[1].Key); Assert.Equal(jobId3, result[2].Key); } [Fact, CleanDatabase] public void AwaitingJobs_ReturnsJobs_WithinTheGivenRange() { // Arrange var jobId1 = SimpleAwaitingJob("1"); var jobId2 = SimpleAwaitingJob("1"); var jobId3 = SimpleAwaitingJob("2"); var jobId4 = SimpleAwaitingJob("1"); var jobId5 = SimpleAwaitingJob("1"); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.AwaitingJobs(1, 2).ToArray(); // Assert Assert.Equal(2, result.Length); Assert.Equal(jobId2, result[0].Key); Assert.Equal(jobId3, result[1].Key); } [Fact, CleanDatabase] public void ScheduledCount_ReturnsZero_WhenThereAreNoScheduledJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.ScheduledCount(); Assert.Equal(0, result); } [Fact, CleanDatabase] public void ScheduledCount_ReturnsTheCorrectNumber_OfScheduledJobs() { // Arrange SimpleScheduledJob(TimeSpan.FromHours(1)); SimpleScheduledJob(TimeSpan.FromHours(2)); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ScheduledCount(); // Assert Assert.Equal(2, result); } [Fact, CleanDatabase] public void EnqueuedCount_ThrowsAnException_WhenQueueNameIsNull() { var monitoring = CreateMonitoringApi(); var exception = Assert.Throws( () => monitoring.EnqueuedCount(null)); Assert.Equal("queue", exception.ParamName); } [Fact, CleanDatabase] public void EnqueuedCount_ReturnsZero_WhenTargetQueueDoesNotExist() { var monitoring = CreateMonitoringApi(); var result = monitoring.EnqueuedCount("critical"); Assert.Equal(0, result); } [Fact, CleanDatabase] public void EnqueuedCount_ReturnsTheCorrectNumber_OfEnqueuedJobs_OfTheGivenQueue() { // Arrange SimpleEnqueueJob("critical"); SimpleEnqueueJob("default"); SimpleEnqueueJob("critical"); SimpleEnqueueJob("critical"); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.EnqueuedCount("critical"); // Assert Assert.Equal(3, result); } [Fact, CleanDatabase] public void FetchedCount_ThrowsAnException_WhenQueueIsNull() { var monitoring = CreateMonitoringApi(); var exception = Assert.Throws( () => monitoring.FetchedCount(null)); Assert.Equal("queue", exception.ParamName); } [Fact, CleanDatabase] public void FetchedCount_ReturnsZero_WhenTargetQueueDoesNotExist() { var monitoring = CreateMonitoringApi(); var result = monitoring.FetchedCount("critical"); Assert.Equal(0, result); } [Fact, CleanDatabase] public void FailedCount_ReturnsZero_WhenThereAreNoFailedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.FailedCount(); Assert.Equal(0, result); } [Fact, CleanDatabase] public void FailedCount_ReturnsTheCorrectNumber_OfFailedJobs() { // Arrange SimpleFailedJob(); SimpleFailedJob(); SimpleFailedJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.FailedCount(); // Assert Assert.Equal(3, result); } [Fact, CleanDatabase] public void ProcessingCount_ReturnsZero_WhenThereAreNoProcessingJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.ProcessingCount(); Assert.Equal(0, result); } [Fact, CleanDatabase] public void ProcessingCount_ReturnsTheCorrectNumber_OfProcessingJobs() { // Arrange SimpleProcessingJob("1", "1"); SimpleProcessingJob("2", "2"); SimpleProcessingJob("3", "3"); SimpleProcessingJob("1", "1"); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.ProcessingCount(); // Assert Assert.Equal(4, result); } [Fact, CleanDatabase] public void SucceededListCount_ReturnsZero_WhenThereAreNoSucceededJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.SucceededListCount(); Assert.Equal(0, result); } [Fact, CleanDatabase] public void SucceededListCount_ReturnsTheCorrectNumber_OfSucceededJobs_CurrentlyInIndex() { // Arrange SimpleSucceededJob(); SimpleSucceededJob(); SimpleSucceededJob(); SimpleSucceededJob(); SimpleSucceededJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.SucceededListCount(); // Assert Assert.Equal(5, result); } [Fact, CleanDatabase] public void DeletedListCount_ReturnsZero_WhenThereAreNoDeletedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.DeletedListCount(); Assert.Equal(0, result); } [Fact, CleanDatabase] public void DeletedListCount_ReturnsTheCorrectNumber_OfDeletedJobs_CurrentlyInIndex() { // Arrange SimpleDeletedJob(); SimpleDeletedJob(); SimpleDeletedJob(); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.DeletedListCount(); // Assert Assert.Equal(3, result); } [Fact, CleanDatabase] public void AwaitingCount_ReturnsZero_WhenThereAreNoAwaitingJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.AwaitingCount(); Assert.Equal(0, result); } [Fact, CleanDatabase] public void AwaitingCount_ReturnsTheCorrectNumber_OfAwaitingJobs() { // Arrange SimpleAwaitingJob("1"); SimpleAwaitingJob("2"); SimpleAwaitingJob("3"); SimpleAwaitingJob("1"); var monitoring = CreateMonitoringApi(); // Act var result = monitoring.AwaitingCount(); // Assert Assert.Equal(4, result); } [Fact, CleanDatabase] public void SucceededByDatesCount_ReturnsEntriesForTheWholeWeek_EvenWhenThereAreNoSucceededJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.SucceededByDatesCount(); var date = _utcNow.Date; Assert.Equal(7, result.Count); Assert.Equal(0, result[date]); Assert.Equal(0, result[date.AddDays(-1)]); Assert.Equal(0, result[date.AddDays(-2)]); Assert.Equal(0, result[date.AddDays(-3)]); Assert.Equal(0, result[date.AddDays(-4)]); Assert.Equal(0, result[date.AddDays(-5)]); Assert.Equal(0, result[date.AddDays(-6)]); } [Fact, CleanDatabase] public void FailedByDatesCount_ReturnsEntriesForTheWholeWeek_EvenWhenThereAreNoFailedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.FailedByDatesCount(); var date = _utcNow.Date; Assert.Equal(7, result.Count); Assert.Equal(0, result[date]); Assert.Equal(0, result[date.AddDays(-1)]); Assert.Equal(0, result[date.AddDays(-2)]); Assert.Equal(0, result[date.AddDays(-3)]); Assert.Equal(0, result[date.AddDays(-4)]); Assert.Equal(0, result[date.AddDays(-5)]); Assert.Equal(0, result[date.AddDays(-6)]); } [Fact, CleanDatabase] public void DeletedByDatesCount_ReturnsEntriesForTheWholeWeek_EvenWhenThereAreNoDeletedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.DeletedByDatesCount(); var date = _utcNow.Date; Assert.Equal(7, result.Count); Assert.Equal(0, result[date]); Assert.Equal(0, result[date.AddDays(-1)]); Assert.Equal(0, result[date.AddDays(-2)]); Assert.Equal(0, result[date.AddDays(-3)]); Assert.Equal(0, result[date.AddDays(-4)]); Assert.Equal(0, result[date.AddDays(-5)]); Assert.Equal(0, result[date.AddDays(-6)]); } [Fact, CleanDatabase] public void HourlySucceededJobs_ReturnsEntriesForTheWholeDay_EvenWhenThereAreNoSucceededJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.HourlySucceededJobs(); Assert.Equal(24, result.Count); Assert.Equal(0, result[result.Keys.ElementAt(0)]); Assert.Equal(0, result[result.Keys.ElementAt(3)]); Assert.Equal(0, result[result.Keys.ElementAt(6)]); Assert.Equal(0, result[result.Keys.ElementAt(9)]); Assert.Equal(0, result[result.Keys.ElementAt(12)]); Assert.Equal(0, result[result.Keys.ElementAt(15)]); Assert.Equal(0, result[result.Keys.ElementAt(18)]); Assert.Equal(0, result[result.Keys.ElementAt(21)]); } [Fact, CleanDatabase] public void HourlyFailedJobs_ReturnsEntriesForTheWholeDay_EvenWhenThereAreNoFailedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.HourlyFailedJobs(); Assert.Equal(24, result.Count); Assert.Equal(0, result[result.Keys.ElementAt(0)]); Assert.Equal(0, result[result.Keys.ElementAt(3)]); Assert.Equal(0, result[result.Keys.ElementAt(6)]); Assert.Equal(0, result[result.Keys.ElementAt(9)]); Assert.Equal(0, result[result.Keys.ElementAt(12)]); Assert.Equal(0, result[result.Keys.ElementAt(15)]); Assert.Equal(0, result[result.Keys.ElementAt(18)]); Assert.Equal(0, result[result.Keys.ElementAt(21)]); } [Fact, CleanDatabase] public void HourlyDeletedJobs_ReturnsEntriesForTheWholeDay_EvenWhenThereAreNoDeletedJobs() { var monitoring = CreateMonitoringApi(); var result = monitoring.HourlyDeletedJobs(); Assert.Equal(24, result.Count); Assert.Equal(0, result[result.Keys.ElementAt(0)]); Assert.Equal(0, result[result.Keys.ElementAt(3)]); Assert.Equal(0, result[result.Keys.ElementAt(6)]); Assert.Equal(0, result[result.Keys.ElementAt(9)]); Assert.Equal(0, result[result.Keys.ElementAt(12)]); Assert.Equal(0, result[result.Keys.ElementAt(15)]); Assert.Equal(0, result[result.Keys.ElementAt(18)]); Assert.Equal(0, result[result.Keys.ElementAt(21)]); } private string SimpleProcessingJob(string serverId = null, string workerId = null, Job job = null) { var type = typeof(ProcessingState); var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic); var processingState = (ProcessingState)ctor.First().Invoke(new object[] { serverId ?? "server-1", workerId ?? "worker-1" }); return SimpleJob(state: processingState, job: job); } private string SimpleScheduledJob(TimeSpan? delay = null, Job job = null) { var state = new ScheduledState(delay ?? TimeSpan.FromHours(1)); return SimpleJob(job: job, state: state, transactionAction: (transaction, assignedJobId, assignedJob) => { transaction.AddToSet( "schedule", assignedJob.Queue != null ? $"{assignedJob.Queue}:{assignedJobId}" : assignedJobId, JobHelper.ToTimestamp(state.EnqueueAt)); }); } private string SimpleEnqueueJob(string queue, string jobId = null, IState state = null, Job job = null, bool noState = false) { return SimpleJob(jobId, job, !noState ? state ?? new EnqueuedState() : null, transactionAction: (transaction, assignedJobId, _) => { transaction.AddToQueue(queue, assignedJobId); }); } private string SimpleSucceededJob(object result = null, long latency = 0, long duration = 0, Job job = null, TimeSpan? expireIn = null) { return SimpleJob( job: job, state: new SucceededState(result, latency, duration), expireIn: expireIn); } private string SimpleFailedJob(Exception exception = null, Job job = null) { return SimpleJob( job: job, state: new FailedState(exception ?? new InvalidOperationException())); } private string SimpleDeletedJob(Exception exception = null, Job job = null, TimeSpan? expireIn = null) { return SimpleJob( job: job, state: new DeletedState( new ExceptionInfo(exception ?? new InvalidOperationException()) ), expireIn: expireIn); } private string SimpleAwaitingJob(string parentId, Job job = null) { return SimpleJob( job: job, state: new AwaitingState(parentId)); } private string SimpleJob(string jobId = null, Job job = null, IState state = null, TimeSpan? expireIn = null, Action transactionAction = null) { var createdId = UseConnection(connection => { job = job ?? Job.FromExpression(() => Empty()); jobId = jobId ?? connection.CreateExpiredJob( job, new Dictionary(), _utcNow, TimeSpan.FromMinutes(1)); using (var transaction = connection.CreateWriteTransaction()) { if (state != null) transaction.SetJobState(jobId, state); transactionAction?.Invoke(transaction, jobId, job); if (expireIn != null) transaction.ExpireJob(jobId, expireIn.Value); transaction.Commit(); } return jobId; }); return createdId; } private SqlServerMonitoringApi CreateMonitoringApi(bool useMicrosoftDataSqlClient = true) { var storage = new SqlServerStorage(() => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)); return new SqlServerMonitoringApi(storage, 1_000); } private T UseConnection(Func action, bool useMicrosoftDataSqlClient = true) { var storage = new SqlServerStorage(() => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)); using (var connection = new SqlServerConnection(storage)) { return action(connection); } } private void UseSqlConnection(Action action, bool useMicrosoftDataSqlClient = true) { using (var connection = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { action(connection); } } private static void AssertWithinSecond(DateTime date1, DateTime? date2) { Assert.NotNull(date2); Assert.True((date1 - date2.Value).TotalSeconds <= 1.0D); } [SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public static void Empty() { } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerStorageFacts.cs ================================================ using Moq; using System; using System.Data.Common; using System.Linq; using Xunit; namespace Hangfire.SqlServer.Tests { public class SqlServerStorageFacts { private readonly SqlServerStorageOptions _options; public SqlServerStorageFacts() { _options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = false }; } [Fact] public void Ctor_ThrowsAnException_WhenConnectionStringIsNull() { var exception = Assert.Throws( () => new SqlServerStorage((string)null)); Assert.Equal("nameOrConnectionString", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenOptionsValueIsNull() { var exception = Assert.Throws( () => new SqlServerStorage("hello", null)); Assert.Equal("options", exception.ParamName); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Ctor_CanCreateSqlServerStorage_WithExistingConnection(bool useMicrosoftDataSqlClient) { using (var connection = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { var storage = new SqlServerStorage(connection); Assert.NotNull(storage); } } [Fact] public void Ctor_ThrowsAnException_WhenConnectionFactoryIsNull() { Func connectionFactory = null; var exception = Assert.Throws( () => new SqlServerStorage(connectionFactory)); Assert.Equal("connectionFactory", exception.ParamName); } [Theory] [InlineData(false), InlineData(true)] public void Ctor_ThrowsAnException_WhenOptionsValueIsNull_WithConnectionFactory(bool useMicrosoftDataSqlClient) { Func connectionFactory = () => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient); var exception = Assert.Throws( () => new SqlServerStorage(connectionFactory, null)); Assert.Equal("options", exception.ParamName); } [Fact] public void CreateAndOpenConnection_UsesConnectionFactory() { var connection = new Mock(); var storage = new SqlServerStorage(() => connection.Object, _options); Assert.Same(connection.Object, storage.CreateAndOpenConnection()); } [Fact, CleanDatabase] public void GetMonitoringApi_ReturnsNonNullInstance() { var storage = CreateStorage(); var api = storage.GetMonitoringApi(); Assert.NotNull(api); } [Fact, CleanDatabase] public void GetConnection_ReturnsNonNullInstance() { var storage = CreateStorage(); using (var connection = (SqlServerConnection)storage.GetConnection()) { Assert.NotNull(connection); } } #if NET452 || NET461 [Fact, CleanDatabase] public void UseConnection_UsesSystemDataSqlClient_ByDefault_OnNet452Only() { var storage = CreateStorage(); storage.UseConnection(null, (_, connection) => { Assert.IsType(connection); }); } #else [Fact, CleanDatabase] public void UseConnection_UsesMicrosoftDataSqlClient_ByDefault() { var storage = CreateStorage(); storage.UseConnection(null, (_, connection) => { Assert.IsType(connection); }); } #endif #if !NET452 [Fact, CleanDatabase] public void UseConnection_UsesSystemDataSqlClient_WhenSqlClientFactoryIsSet() { _options.SqlClientFactory = System.Data.SqlClient.SqlClientFactory.Instance; var storage = CreateStorage(); storage.UseConnection(null, (_, connection) => { Assert.IsType(connection); }); } #endif [Fact, CleanDatabase] public void GetComponents_ReturnsAllNeededComponents() { var storage = CreateStorage(); var components = storage.GetComponents(); var componentTypes = components.Select(x => x.GetType()).ToArray(); Assert.Contains(typeof(ExpirationManager), componentTypes); } [Fact, CleanDatabase] public void HasFeature_Connection_GetUtcDateTime_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Connection.GetUtcDateTime"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Connection_GetSetContains_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Connection.GetSetContains"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Connection_GetSetCount_Limited_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Connection.GetSetCount.Limited"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Connection_BatchedGetFirstByLowestScoreFromSet_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Connection.BatchedGetFirstByLowestScoreFromSet"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Job_Queue_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Job.Queue"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Storage_ExtendedApi_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Storage.ExtendedApi"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Transaction_AcquireDistributedLock_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Transaction.AcquireDistributedLock"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Monitoring_AwaitingJobs_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Monitoring.AwaitingJobs"); Assert.True(result); } [Fact, CleanDatabase] public void HasFeature_Monitoring_DeletedStateGraphs_ReturnsTrue() { var storage = CreateStorage(); var result = storage.HasFeature("Monitoring.DeletedStateGraphs"); Assert.True(result); } private SqlServerStorage CreateStorage() { return new SqlServerStorage( ConnectionUtils.GetConnectionString(), _options); } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerTimeoutJobFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Data; using System.Linq; using System.Threading; using ReferencedDapper::Dapper; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Tests { public class SqlServerTimeoutJobFacts { private const string JobId = "id"; private const string Queue = "queue"; private static readonly DateTime FetchedAt = DateTime.UtcNow; [Fact] public void Ctor_ThrowsAnException_WhenConnectionIsNull() { var exception = Assert.Throws( () => new SqlServerTimeoutJob(null, 1, JobId, Queue, FetchedAt)); Assert.Equal("storage", exception.ParamName); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Ctor_ThrowsAnException_WhenJobIdIsNull(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { var exception = Assert.Throws( () => new SqlServerTimeoutJob(storage, 1, null, Queue, FetchedAt)); Assert.Equal("jobId", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Ctor_ThrowsAnException_WhenQueueIsNull(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { var exception = Assert.Throws( () => new SqlServerTimeoutJob(storage, 1, JobId, null, FetchedAt)); Assert.Equal("queue", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Ctor_CorrectlySets_AllInstanceProperties(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { using (var fetchedJob = new SqlServerTimeoutJob(storage, 1, JobId, Queue, FetchedAt)) { Assert.Equal(1, fetchedJob.Id); Assert.Equal(JobId, fetchedJob.JobId); Assert.Equal(Queue, fetchedJob.Queue); Assert.Equal(FetchedAt, fetchedJob.FetchedAt); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RemoveFromQueue_ReallyDeletesTheJobFromTheQueue(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { // Arrange var id = CreateJobQueueRecord(sql, "1", "default", FetchedAt); using (var processingJob = new SqlServerTimeoutJob(storage, id, "1", "default", FetchedAt)) { processingJob.DisposeTimer(); // Act processingJob.RemoveFromQueue(); // Assert var count = sql.Query($"select count(*) from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Equal(0, count); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RemoveFromQueue_DoesNotDelete_UnrelatedJobs(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { // Arrange CreateJobQueueRecord(sql, "1", "default", FetchedAt); CreateJobQueueRecord(sql, "1", "critical", FetchedAt); CreateJobQueueRecord(sql, "2", "default", FetchedAt); using (var fetchedJob = new SqlServerTimeoutJob(storage, 999, "1", "default", FetchedAt)) { fetchedJob.DisposeTimer(); // Act fetchedJob.RemoveFromQueue(); // Assert var count = sql.Query($"select count(*) from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Equal(3, count); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Requeue_SetsFetchedAtValueToNull(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { // Arrange var id = CreateJobQueueRecord(sql, "1", "default", FetchedAt); using (var processingJob = new SqlServerTimeoutJob(storage, id, "1", "default", FetchedAt)) { processingJob.DisposeTimer(); // Act processingJob.Requeue(); // Assert var record = sql.Query($"select * from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Null(record.FetchedAt); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Timer_UpdatesFetchedAtColumn(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { // Arrange var id = CreateJobQueueRecord(sql, "1", "default", FetchedAt); using (var processingJob = new SqlServerTimeoutJob(storage, id, "1", "default", FetchedAt)) { processingJob.DisposeTimer(); Thread.Sleep(TimeSpan.FromSeconds(2)); processingJob.ExecuteKeepAliveQueryIfRequired(); var record = sql.Query($"select * from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.NotNull(processingJob.FetchedAt); Assert.Equal(processingJob.FetchedAt, record.FetchedAt); var now = DateTime.UtcNow; Assert.True(now.AddSeconds(-5) < record.FetchedAt, (now - record.FetchedAt).ToString()); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RemoveFromQueue_AfterTimer_RemovesJobFromTheQueue(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { // Arrange var id = CreateJobQueueRecord(sql, "1", "default", FetchedAt); using (var processingJob = new SqlServerTimeoutJob(storage, id, "1", "default", FetchedAt)) { Thread.Sleep(TimeSpan.FromSeconds(1)); processingJob.DisposeTimer(); // Act processingJob.RemoveFromQueue(); // Assert var count = sql.Query($"select count(*) from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Equal(0, count); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void RequeueQueue_AfterTimer_SetsFetchedAtValueToNull(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { // Arrange var id = CreateJobQueueRecord(sql, "1", "default", FetchedAt); using (var processingJob = new SqlServerTimeoutJob(storage, id, "1", "default", FetchedAt)) { Thread.Sleep(TimeSpan.FromSeconds(1)); processingJob.DisposeTimer(); // Act processingJob.Requeue(); // Assert var record = sql.Query($"select * from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Null(record.FetchedAt); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false), InlineData(true)] public void Dispose_SetsFetchedAtValueToNull_IfThereWereNoCallsToComplete(bool useMicrosoftDataSqlClient) { UseConnection((sql, storage) => { // Arrange var id = CreateJobQueueRecord(sql, "1", "default", FetchedAt); using (var processingJob = new SqlServerTimeoutJob(storage, id, "1", "default", FetchedAt)) { // Act processingJob.Dispose(); // Assert var record = sql.Query($"select * from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Null(record.FetchedAt); } }, useMicrosoftDataSqlClient); } private static int CreateJobQueueRecord(IDbConnection connection, string jobId, string queue, DateTime? fetchedAt) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].JobQueue (JobId, Queue, FetchedAt) values (@id, @queue, @fetchedAt); select scope_identity() as Id"; return (int)connection.Query(arrangeSql, new { id = jobId, queue, fetchedAt }).Single().Id; } private static void UseConnection(Action action, bool useMicrosoftDataSqlClient) { var storage = new SqlServerStorage( () => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient), new SqlServerStorageOptions { SlidingInvisibilityTimeout = TimeSpan.FromSeconds(5) }); using (var connection = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { action(connection, storage); } } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerTransactionJobFacts.cs ================================================ using System; using System.Data; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Tests { public class SqlServerTransactionJobFacts { private const string JobId = "id"; private const string Queue = "queue"; private readonly Mock _connection; private readonly Mock _transaction; private readonly Mock _storage; public SqlServerTransactionJobFacts() { _connection = new Mock(); _transaction = new Mock(); _storage = new Mock(ConnectionUtils.GetConnectionString()); } [Fact] public void Ctor_ThrowsAnException_WhenStorageIsNull() { var exception = Assert.Throws( () => new SqlServerTransactionJob(null, _connection.Object, _transaction.Object, JobId, Queue)); Assert.Equal("storage", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenConnectionIsNull() { var exception = Assert.Throws( () => new SqlServerTransactionJob(_storage.Object, null, _transaction.Object, JobId, Queue)); Assert.Equal("connection", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenTransactionIsNull() { var exception = Assert.Throws( () => new SqlServerTransactionJob(_storage.Object, _connection.Object, null, JobId, Queue)); Assert.Equal("transaction", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenJobIdIsNull() { var exception = Assert.Throws( () => new SqlServerTransactionJob(_storage.Object, _connection.Object, _transaction.Object, null, Queue)); Assert.Equal("jobId", exception.ParamName); } [Fact] public void Ctor_ThrowsAnException_WhenQueueIsNull() { var exception = Assert.Throws( () => new SqlServerTransactionJob(_storage.Object, _connection.Object, _transaction.Object, JobId, null)); Assert.Equal("queue", exception.ParamName); } [Fact] public void Ctor_CorrectlySets_AllInstanceProperties() { var fetchedJob = new SqlServerTransactionJob(_storage.Object, _connection.Object, _transaction.Object, JobId, Queue); Assert.Equal(JobId, fetchedJob.JobId); Assert.Equal(Queue, fetchedJob.Queue); } [Fact] public void RemoveFromQueue_CommitsTheTransaction() { // Arrange var processingJob = CreateFetchedJob("1", "default"); // Act processingJob.RemoveFromQueue(); // Assert _transaction.Verify(x => x.Commit()); } [Fact] public void Requeue_RollsbackTheTransaction() { // Arrange var processingJob = CreateFetchedJob("1", "default"); // Act processingJob.Requeue(); // Assert _transaction.Verify(x => x.Rollback()); } [Fact] public void Dispose_DisposesTheTransactionAndConnection() { var processingJob = CreateFetchedJob("1", "queue"); // Act processingJob.Dispose(); // Assert _transaction.Verify(x => x.Dispose()); _connection.Verify(x => x.Dispose()); } private SqlServerTransactionJob CreateFetchedJob(string jobId, string queue) { return new SqlServerTransactionJob(_storage.Object, _connection.Object, _transaction.Object, jobId, queue); } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/SqlServerWriteOnlyTransactionFacts.cs ================================================ extern alias ReferencedDapper; using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; using System.Threading; using ReferencedDapper::Dapper; using Hangfire.States; using Hangfire.Storage; using Moq; using Xunit; // ReSharper disable AssignNullToNotNullAttribute namespace Hangfire.SqlServer.Tests { public class SqlServerWriteOnlyTransactionFacts { private static readonly string TooLongKey = "123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789_12345"; private static readonly string TooLongTruncatedKey = TooLongKey.Substring(0, 100); private readonly PersistentJobQueueProviderCollection _queueProviders; public SqlServerWriteOnlyTransactionFacts() { var defaultProvider = new Mock(); defaultProvider.Setup(x => x.GetJobQueue()) .Returns(new Mock().Object); _queueProviders = new PersistentJobQueueProviderCollection(defaultProvider.Object); } public static IEnumerable GetConfiguration() { yield return new object[] { /* useBatching */ false, /* useMicrosoftDataSqlClient */ false, /* disableTransactionScope */ false }; yield return new object[] { /* useBatching */ false, /* useMicrosoftDataSqlClient */ true, /* disableTransactionScope */ false }; yield return new object[] { /* useBatching */ true, /* useMicrosoftDataSqlClient */ false, /* disableTransactionScope */ false }; yield return new object[] { /* useBatching */ true, /* useMicrosoftDataSqlClient */ true, /* disableTransactionScope */ false }; #if NET452 || NET461 if (IsRunningOnWindows()) // TransactionScope isn't used on non-Windows platforms { yield return new object[] { /* useBatching */ false, /* useMicrosoftDataSqlClient */ false, /* disableTransactionScope */ true }; yield return new object[] { /* useBatching */ false, /* useMicrosoftDataSqlClient */ true, /* disableTransactionScope */ true }; yield return new object[] { /* useBatching */ true, /* useMicrosoftDataSqlClient */ false, /* disableTransactionScope */ true }; yield return new object[] { /* useBatching */ true, /* useMicrosoftDataSqlClient */ true, /* disableTransactionScope */ true }; } #endif } [Fact] public void Ctor_ThrowsAnException_IfConnectionIsNull() { var exception = Assert.Throws( () => new SqlServerWriteOnlyTransaction(null)); Assert.Equal("connection", exception.ParamName); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireJob_ThrowsAnException_WhenJobIdIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.ExpireJob(null, TimeSpan.Zero), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("jobId", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireJob_SetsJobExpirationData(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnection(sql => { var jobId = sql.Query(arrangeSql).Single().Id.ToString(); var anotherJobId = sql.Query(arrangeSql).Single().Id.ToString(); Commit(x => x.ExpireJob(jobId, TimeSpan.FromHours(24)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, jobId); Assert.True(DateTime.UtcNow.AddHours(23) < job.ExpireAt && job.ExpireAt < DateTime.UtcNow.AddHours(25)); var anotherJob = GetTestJob(sql, anotherJobId); Assert.Null(anotherJob.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistJob_ThrowsAnException_WhenJobIdIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.PersistJob(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("jobId", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistJob_ClearsTheJobExpirationData(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt, ExpireAt) values ('', '', getutcdate(), getutcdate()) select scope_identity() as Id"; UseConnection(sql => { var jobId = sql.Query(arrangeSql).Single().Id.ToString(); var anotherJobId = sql.Query(arrangeSql).Single().Id.ToString(); Commit(x => x.PersistJob(jobId), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, jobId); Assert.Null(job.ExpireAt); var anotherJob = GetTestJob(sql, anotherJobId); Assert.NotNull(anotherJob.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetJobState_ThrowsAnException_WhenJobIdIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.SetJobState(null, new Mock().Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("jobId", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetJobState_ThrowsAnException_WhenStateIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.SetJobState("my-job", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("state", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetJobState_AppendsAStateAndSetItToTheJob(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnection(sql => { var jobId = sql.Query(arrangeSql).Single().Id.ToString(); var anotherJobId = sql.Query(arrangeSql).Single().Id.ToString(); var state = new Mock(); state.Setup(x => x.Name).Returns("State"); state.Setup(x => x.Reason).Returns("Reason"); state.Setup(x => x.SerializeData()) .Returns(new Dictionary { { "Name", "Value" } }); Commit(x => x.SetJobState(jobId, state.Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, jobId); Assert.Equal("State", job.StateName); Assert.NotNull(job.StateId); var anotherJob = GetTestJob(sql, anotherJobId); Assert.Null(anotherJob.StateName); Assert.Null(anotherJob.StateId); var jobState = sql.Query($"select * from [{Constants.DefaultSchema}].State").Single(); Assert.Equal((string)jobId, jobState.JobId.ToString()); Assert.Equal("State", jobState.Name); Assert.Equal("Reason", jobState.Reason); Assert.NotNull(jobState.CreatedAt); Assert.Equal("{\"Name\":\"Value\"}", jobState.Data); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetJobState_CanBeCalledWithNullReasonAndData(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnection(sql => { var jobId = sql.Query(arrangeSql).Single().Id.ToString(); var state = new Mock(); state.Setup(x => x.Name).Returns("State"); state.Setup(x => x.Reason).Returns((string)null); state.Setup(x => x.SerializeData()).Returns((Dictionary)null); Commit(x => x.SetJobState(jobId, state.Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, jobId); Assert.Equal("State", job.StateName); Assert.NotNull(job.StateId); var jobState = sql.Query($"select * from [{Constants.DefaultSchema}].State").Single(); Assert.Equal("State", jobState.Name); Assert.Equal(null, jobState.Reason); Assert.Equal(null, jobState.Data); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddJobState_ThrowsAnException_WhenJobIdIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddJobState(null, new Mock().Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("jobId", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddJobState_ThrowsAnException_WhenStateIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddJobState("my-job", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("state", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddJobState_JustAddsANewRecordInATable(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnection(sql => { var jobId = sql.Query(arrangeSql).Single().Id.ToString(); var state = new Mock(); state.Setup(x => x.Name).Returns("State"); state.Setup(x => x.Reason).Returns("Reason"); state.Setup(x => x.SerializeData()) .Returns(new Dictionary { { "Name", "Value" } }); Commit(x => x.AddJobState(jobId, state.Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, jobId); Assert.Null(job.StateName); Assert.Null(job.StateId); var jobState = sql.Query($"select * from [{Constants.DefaultSchema}].State").Single(); Assert.Equal((string)jobId, jobState.JobId.ToString()); Assert.Equal("State", jobState.Name); Assert.Equal("Reason", jobState.Reason); Assert.NotNull(jobState.CreatedAt); Assert.Equal("{\"Name\":\"Value\"}", jobState.Data); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddJobState_CanBeCalledWithNullReasonAndData(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Job (InvocationData, Arguments, CreatedAt) values ('', '', getutcdate()) select scope_identity() as Id"; UseConnection(sql => { var jobId = sql.Query(arrangeSql).Single().Id.ToString(); var state = new Mock(); state.Setup(x => x.Name).Returns("State"); state.Setup(x => x.Reason).Returns((string)null); state.Setup(x => x.SerializeData()).Returns((Dictionary)null); Commit(x => x.AddJobState(jobId, state.Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, jobId); Assert.Null(job.StateName); Assert.Null(job.StateId); var jobState = sql.Query($"select * from [{Constants.DefaultSchema}].State").Single(); Assert.Equal((string)jobId, jobState.JobId.ToString()); Assert.Equal("State", jobState.Name); Assert.Equal(null, jobState.Reason); Assert.NotNull(jobState.CreatedAt); Assert.Equal(null, jobState.Data); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToQueue_ThrowsAnException_WhenQueueIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddToQueue(null, "my-job"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("queue", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToQueue_ThrowsAnException_WhenJobIdIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddToQueue("my-queue", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("jobId", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [InlineData(false, false)] [InlineData(false, true)] [InlineData(true, false)] [InlineData(true, true)] public void AddToQueue_CallsEnqueue_OnTargetPersistentQueue(bool useBatching, bool useMicrosoftDataSqlClient) { var correctJobQueue = new Mock(); var correctProvider = new Mock(); correctProvider.Setup(x => x.GetJobQueue()) .Returns(correctJobQueue.Object); _queueProviders.Add(correctProvider.Object, new[] { "default" }); UseConnection(sql => { // External providers support DisableTransactionScope parameter in .NET Framework. Commit(x => x.AddToQueue("default", "1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope: false); correctJobQueue.Verify(x => x.Enqueue( #if NETCOREAPP It.IsNotNull(), It.IsNotNull(), #else It.IsNotNull(), #endif "default", "1")); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToQueue_EnqueuesAJobDirectly_WhenDefaultQueueProviderIsUsed(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { // We are relying on the fact that SqlServerJobQueue.Enqueue method will throw with a negative // timeout. If we don't see this exception, and if the record is inserted, then everything is fine. var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = false, CommandTimeout = TimeSpan.FromSeconds(-5) }; _queueProviders.Add( new SqlServerJobQueueProvider(new Mock("connection=false;", options).Object, options), new [] { "default" }); UseConnection(sql => { Commit(x => x.AddToQueue("default", "1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].JobQueue").Single(); Assert.Equal("1", record.JobId.ToString()); Assert.Equal("default", record.Queue); Assert.Null(record.FetchedAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void Enqueue_ThrowsAnException_WhenTheGivenQueueIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(connection => { var queueName = "some-really-long-queue-name-that-should-cause-an-exception-to-be-thrown-and-not-ignored"; // We are relying on the fact that SqlServerJobQueue.Enqueue method will throw with a negative // timeout. If we don't see this exception, and if the record is inserted, then everything is fine. var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = false, CommandTimeout = TimeSpan.FromSeconds(-5) }; _queueProviders.Add( new SqlServerJobQueueProvider(new Mock("connection=false;", options).Object, options), new [] { queueName }); var exception = Assert.ThrowsAny(() => { Commit(x => x.AddToQueue(queueName, "1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); }); var record = connection.Query($"select * from [{Constants.DefaultSchema}].JobQueue").SingleOrDefault(); Assert.Null(record); Assert.StartsWith("String or binary data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } private static dynamic GetTestJob(IDbConnection connection, string jobId) { return connection .Query($"select * from [{Constants.DefaultSchema}].Job where Id = @id", new { id = jobId }) .Single(); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void IncrementCounter_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.IncrementCounter(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void IncrementCounter_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.IncrementCounter(TooLongKey), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void IncrementCounter_AddsRecordToCounterTable_WithPositiveValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.IncrementCounter("my-key"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].Counter").Single(); Assert.Equal("my-key", record.Key); Assert.Equal(1, record.Value); Assert.Equal((DateTime?)null, record.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void IncrementCounter_WithExpiry_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.IncrementCounter(null, TimeSpan.FromHours(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void IncrementCounter_WithExpiry_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.IncrementCounter(TooLongKey, TimeSpan.FromHours(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void IncrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.IncrementCounter("my-key", TimeSpan.FromDays(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].Counter").Single(); Assert.Equal("my-key", record.Key); Assert.Equal(1, record.Value); Assert.NotNull(record.ExpireAt); var expireAt = (DateTime) record.ExpireAt; Assert.True(DateTime.UtcNow.AddHours(23) < expireAt); Assert.True(expireAt < DateTime.UtcNow.AddHours(25)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void IncrementCounter_WithExistingKey_AddsAnotherRecord(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.IncrementCounter("my-key"); x.IncrementCounter("my-key"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].Counter").Single(); Assert.Equal(2, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void DecrementCounter_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.DecrementCounter(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void DecrementCounter_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.DecrementCounter(TooLongKey), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void DecrementCounter_AddsRecordToCounterTable_WithNegativeValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.DecrementCounter("my-key"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].Counter").Single(); Assert.Equal("my-key", record.Key); Assert.Equal(-1, record.Value); Assert.Equal((DateTime?)null, record.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void DecrementCounter_WithExpiry_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.DecrementCounter(null, TimeSpan.FromHours(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void DecrementCounter_WithExpiry_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.DecrementCounter(TooLongKey, TimeSpan.FromHours(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void DecrementCounter_WithExpiry_AddsARecord_WithExpirationTimeSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.DecrementCounter("my-key", TimeSpan.FromDays(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].Counter").Single(); Assert.Equal("my-key", record.Key); Assert.Equal(-1, record.Value); Assert.NotNull(record.ExpireAt); var expireAt = (DateTime)record.ExpireAt; Assert.True(DateTime.UtcNow.AddHours(23) < expireAt); Assert.True(expireAt < DateTime.UtcNow.AddHours(25)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void DecrementCounter_WithExistingKey_AddsAnotherRecord(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.DecrementCounter("my-key"); x.DecrementCounter("my-key"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].Counter").Single(); Assert.Equal(2, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddToSet(null, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_ThrowsAnException_WhenValueIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddToSet("my-set", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("value", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.AddToSet(TooLongKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_AddsARecord_IfThereIsNo_SuchKeyAndValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.AddToSet("my-key", "my-value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal("my-key", record.Key); Assert.Equal("my-value", record.Value); Assert.Equal(0.0, record.Score, 2); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_AddsARecord_WhenKeyIsExists_ButValuesAreDifferent(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.AddToSet("my-key", "my-value"); x.AddToSet("my-key", "another-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal(2, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_DoesNotAddARecord_WhenBothKeyAndValueAreExist(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.AddToSet("my-key", "my-value"); x.AddToSet("my-key", "my-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal(1, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithScore_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddToSet(null, "value", 1.2D), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithScore_ThrowsAnException_WhenValueIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddToSet("my-set", null, 1.2D), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("value", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithScore_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.AddToSet(TooLongKey, "value", 1.2D), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithScore_AddsARecordWithScore_WhenBothKeyAndValueAreNotExist(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.AddToSet("my-key", "my-value", 3.2), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal("my-key", record.Key); Assert.Equal("my-value", record.Value); Assert.Equal(3.2, record.Score, 3); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithScore_UpdatesAScore_WhenBothKeyAndValueAreExist(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.AddToSet("my-key", "my-value"); x.AddToSet("my-key", "my-value", 3.2); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal(3.2, record.Score, 3); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithIgnoreDupKeyOption_InsertsNonExistingValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { try { UseConnection(sql => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON)"); Commit(x => x.AddToSet("my-key","my-value", 3.2), useMicrosoftDataSqlClient, useBatching, disableTransactionScope, options => options.UseIgnoreDupKeyOption = true); var record = sql.Query($"select * from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal(3.2, record.Score, 3); }, useMicrosoftDataSqlClient); } finally { UseConnection(sql => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithIgnoreDupKeyOption_UpdatesExistingValue_WhenIgnoreDupKeyOptionIsSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { try { UseConnection(sql => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON)"); sql.Execute($@"insert into [{Constants.DefaultSchema}].[Set] ([Key], Value, Score) VALUES (N'my-key1', N'value1', 1.2), (N'my-key1', N'value2', 1.2), (N'my-key2', N'value1', 1.2)"); Commit(x => x.AddToSet("my-key1", "value1", 2.3), useMicrosoftDataSqlClient, useBatching, disableTransactionScope, options => options.UseIgnoreDupKeyOption = true); var record1 = sql.Query($"select * from [{Constants.DefaultSchema}].[Set] where [Key] = N'my-key1' and Value = N'value1'").Single(); Assert.Equal(2.3, record1.Score, 3); var record2 = sql.Query($"select * from [{Constants.DefaultSchema}].[Set] where [Key] = N'my-key1' and Value = N'value2'").Single(); Assert.Equal(1.2, record2.Score, 3); var record3 = sql.Query($"select * from [{Constants.DefaultSchema}].[Set] where [Key] = N'my-key2' and Value = N'value1'").Single(); Assert.Equal(1.2, record3.Score, 3); }, useMicrosoftDataSqlClient); } finally { UseConnection(sql => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddToSet_WithIgnoreDupKeyOption_FailsToUpdateExistingValue_WhenIgnoreDupKeyOptionWasNotSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { try { UseConnection(sql => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = OFF)"); sql.Execute($"insert into [{Constants.DefaultSchema}].[Set] ([Key], Value, Score) VALUES (N'key1', N'value1', 1.2)"); var exception = Assert.ThrowsAny(() => Commit(x => x.AddToSet("key1", "value1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope, options => options.UseIgnoreDupKeyOption = true)); Assert.Contains("Violation of PRIMARY KEY", exception.Message); }, useMicrosoftDataSqlClient); } finally { UseConnection(sql => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromSet_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.RemoveFromSet(null, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromSet_ThrowsAnException_WhenValueIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.RemoveFromSet("my-set", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("value", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromSet_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.AddToSet(TooLongTruncatedKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.RemoveFromSet(TooLongKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [Value] from [{Constants.DefaultSchema}].[Set] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Equal("value", result.Value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromSet_RemovesARecord_WithGivenKeyAndValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.AddToSet("my-key", "my-value"); x.RemoveFromSet("my-key", "my-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal(0, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromSet_DoesNotRemoveRecord_WithSameKey_AndDifferentValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.AddToSet("my-key", "my-value"); x.RemoveFromSet("my-key", "different-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal(1, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromSet_DoesNotRemoveRecord_WithSameValue_AndDifferentKey(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.AddToSet("my-key", "my-value"); x.RemoveFromSet("different-key", "my-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal(1, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void InsertToList_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.InsertToList(null, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void InsertToList_ThrowsAnException_WhenValueIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.InsertToList("my-list", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("value", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void InsertToList_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.InsertToList(TooLongKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void InsertToList_AddsARecord_WithGivenValues(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.InsertToList("my-key", "my-value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].List").Single(); Assert.Equal("my-key", record.Key); Assert.Equal("my-value", record.Value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void InsertToList_AddsAnotherRecord_WhenBothKeyAndValueAreExist(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "my-value"); x.InsertToList("my-key", "my-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(2, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromList_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.RemoveFromList(null, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromList_ThrowsAnException_WhenValueIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.RemoveFromList("my-list", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("value", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromList_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.InsertToList(TooLongTruncatedKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.RemoveFromList(TooLongKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [Value] from [{Constants.DefaultSchema}].[List] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Equal("value", result.Value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromList_RemovesAllRecords_WithGivenKeyAndValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "my-value"); x.InsertToList("my-key", "my-value"); x.RemoveFromList("my-key", "my-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(0, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromList_DoesNotRemoveRecords_WithSameKey_ButDifferentValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "my-value"); x.RemoveFromList("my-key", "different-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(1, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveFromList_DoesNotRemoveRecords_WithSameValue_ButDifferentKey(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "my-value"); x.RemoveFromList("different-key", "my-value"); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(1, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void TrimList_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.TrimList(null, 0, 1), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void TrimList_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.InsertToList(TooLongTruncatedKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.TrimList(TooLongKey, 1, 2), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [Value] from [{Constants.DefaultSchema}].[List] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Equal("value", result.Value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void TrimList_TrimsAList_ToASpecifiedRange(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "0"); x.InsertToList("my-key", "1"); x.InsertToList("my-key", "2"); x.InsertToList("my-key", "3"); x.TrimList("my-key", 1, 2); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var records = sql.Query($"select * from [{Constants.DefaultSchema}].List").ToArray(); Assert.Equal(2, records.Length); Assert.Equal("1", records[0].Value); Assert.Equal("2", records[1].Value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void TrimList_RemovesRecordsToEnd_IfKeepAndingAt_GreaterThanMaxElementIndex(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "0"); x.InsertToList("my-key", "1"); x.InsertToList("my-key", "2"); x.TrimList("my-key", 1, 100); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(2, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void TrimList_RemovesAllRecords_WhenStartingFromValue_GreaterThanMaxElementIndex(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "0"); x.TrimList("my-key", 1, 100); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(0, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void TrimList_RemovesAllRecords_IfStartFromGreaterThanEndingAt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "0"); x.TrimList("my-key", 1, 0); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(0, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void TrimList_RemovesRecords_OnlyOfAGivenKey(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => { x.InsertToList("my-key", "0"); x.TrimList("another-key", 1, 0); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var recordCount = sql.Query($"select count(*) from [{Constants.DefaultSchema}].List").Single(); Assert.Equal(1, recordCount); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.SetRangeInHash(null, new Dictionary()), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_ThrowsAnException_WhenKeyValuePairsArgumentIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.SetRangeInHash("some-hash", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("keyValuePairs", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.SetRangeInHash( TooLongKey, new Dictionary { { "field", "value" } }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_MergesAllRecords(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.SetRangeInHash("some-hash", new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" } }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var result = sql.Query( $"select * from [{Constants.DefaultSchema}].Hash where [Key] = @key", new { key = "some-hash" }) .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("Value1", result["Key1"]); Assert.Equal("Value2", result["Key2"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_CanSetANullValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Commit(x => x.SetRangeInHash("some-hash", new Dictionary { { "Key1", null } }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var result = sql.Query( $"select * from [{Constants.DefaultSchema}].Hash where [Key] = @key", new { key = "some-hash" }) .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Null(result["Key1"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_WithIgnoreDupKeyOption_InsertsNonExistingValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { try { UseConnection(sql => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"); Commit(x => x.SetRangeInHash("some-hash", new Dictionary { { "key", "value" } }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope, options => options.UseIgnoreDupKeyOption = true); var result = sql .Query($"select * from [{Constants.DefaultSchema}].Hash where [Key] = N'some-hash'") .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("value", result["key"]); }, useMicrosoftDataSqlClient); } finally { UseConnection(sql => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_WithIgnoreDupKeyOption_UpdatesExistingValue_WhenIgnoreDupKeyOptionIsSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { try { UseConnection(sql => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"); sql.Execute($@"insert into [{Constants.DefaultSchema}].Hash([Key], Field, Value) VALUES (N'some-hash', N'key1', N'value1'), (N'some-hash', N'key2', N'value1'), (N'othr-hash', N'key1', N'value1')"); Commit(x => x.SetRangeInHash("some-hash", new Dictionary { { "key1", "value2" } }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope, options => options.UseIgnoreDupKeyOption = true); var someResult = sql .Query($"select * from [{Constants.DefaultSchema}].Hash where [Key] = N'some-hash'") .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("value2", someResult["key1"]); Assert.Equal("value1", someResult["key2"]); var othrResult = sql .Query($"select * from [{Constants.DefaultSchema}].Hash where [Key] = N'othr-hash'") .ToDictionary(x => (string)x.Field, x => (string)x.Value); Assert.Equal("value1", othrResult["key1"]); }, useMicrosoftDataSqlClient); } finally { UseConnection(sql => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetRangeInHash_WithIgnoreDupKeyOption_FailsToUpdateExistingValue_WhenIgnoreDupKeyOptionWasNotSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { try { UseConnection(sql => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = OFF)"); sql.Execute($"insert into [{Constants.DefaultSchema}].Hash([Key], Field, Value) VALUES (N'some-hash', N'key', N'value1')"); var exception = Assert.ThrowsAny(() => Commit(x => x.SetRangeInHash("some-hash", new Dictionary { { "key", "value2" } }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope, options => options.UseIgnoreDupKeyOption = true)); Assert.Contains("Violation of PRIMARY KEY", exception.Message); }, useMicrosoftDataSqlClient); } finally { UseConnection(sql => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Hash] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveHash_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Assert.Throws( () => Commit(x => x.RemoveHash(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveHash_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.SetRangeInHash( TooLongTruncatedKey, new Dictionary {{ "field", "value" }}), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.RemoveHash(TooLongKey), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [Value] from [{Constants.DefaultSchema}].[Hash] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Equal("value", result.Value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveHash_RemovesAllHashRecords(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.SetRangeInHash("some-hash", new Dictionary { { "Key1", "Value1" }, { "Key2", "Value2" } }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.RemoveHash("some-hash"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var count = sql.Query($"select count(*) from [{Constants.DefaultSchema}].Hash").Single(); Assert.Equal(0, count); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddRangeToSet_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddRangeToSet(null, new List()), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddRangeToSet_ThrowsAnException_WhenKeyIsTooLong(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.ThrowsAny(() => Commit(x => x.AddRangeToSet( TooLongKey, new List { "field" }), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Contains("data would be truncated", exception.Message); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddRangeToSet_ThrowsAnException_WhenItemsValueIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.AddRangeToSet("my-set", null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("items", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddRangeToSet_AddsAllItems_ToAGivenSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var items = new List { "1", "2", "3" }; Commit(x => x.AddRangeToSet("my-set", items), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var records = sql.Query($"select [Value] from [{Constants.DefaultSchema}].[Set] where [Key] = N'my-set'"); Assert.Equal(items, records); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddRangeToSet_DoesNotFailWithException_WhenIgnoreDupKeyOptionIsSet(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { try { UseConnection(sql => { sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON)"); sql.Execute($@"insert into [{Constants.DefaultSchema}].[Set] ([Key], Value, Score) VALUES (N'my-set', N'2', 1.2), (N'my-set', N'3', 1.2), (N'my-set', N'4', 1.2)"); var items = new List { "1", "2", "3" }; Commit(x => x.AddRangeToSet("my-set", items), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var records = sql.Query($"select [Value] from [{Constants.DefaultSchema}].[Set] where [Key] = N'my-set'"); Assert.Equal(new List { "1", "2", "3", "4" }, records); }, useMicrosoftDataSqlClient); } finally { UseConnection(sql => sql.Execute($"ALTER TABLE [{Constants.DefaultSchema}].[Set] REBUILD WITH (IGNORE_DUP_KEY = ON)"), useMicrosoftDataSqlClient); } } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveSet_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { Assert.Throws( () => Commit(x => x.RemoveSet(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveSet_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.AddToSet(TooLongTruncatedKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.RemoveSet(TooLongKey), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [Value] from [{Constants.DefaultSchema}].[Set] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Equal("value", result.Value); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void RemoveSet_RemovesASet_WithAGivenKey(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@key, @value, 0.0)"; UseConnection(sql => { sql.Execute(arrangeSql, new [] { new { key = "set-1", value = "1" }, new { key = "set-2", value = "1" } }); Commit(x => x.RemoveSet("set-1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].[Set]").Single(); Assert.Equal("set-2", record.Key); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireHash_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.ExpireHash(null, TimeSpan.FromMinutes(5)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireHash_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.SetRangeInHash( TooLongTruncatedKey, new Dictionary {{ "field", "value" }}), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.ExpireHash(TooLongKey, TimeSpan.FromHours(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [ExpireAt] from [{Constants.DefaultSchema}].[Hash] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Null(result.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireHash_SetsExpirationTimeOnAHash_WithGivenKey(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Hash ([Key], [Field]) values (@key, @field)"; UseConnection(sql => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "hash-1", field = "field" }, new { key = "hash-2", field = "field" } }); // Act Commit(x => x.ExpireHash("hash-1", TimeSpan.FromMinutes(60)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var records = sql.Query($"select * from [{Constants.DefaultSchema}].Hash").ToDictionary(x => (string)x.Key, x => (DateTime?)x.ExpireAt); Assert.True(DateTime.UtcNow.AddMinutes(59) < records["hash-1"]); Assert.True(records["hash-1"] < DateTime.UtcNow.AddMinutes(61)); Assert.Null(records["hash-2"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireSet_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.ExpireSet(null, TimeSpan.FromSeconds(45)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireSet_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.AddToSet(TooLongTruncatedKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.ExpireSet(TooLongKey, TimeSpan.FromHours(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [ExpireAt] from [{Constants.DefaultSchema}].[Set] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Null(result.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireSet_SetsExpirationTime_OnASet_WithGivenKey(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [Score]) values (@key, @value, 0.0)"; UseConnection(sql => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "set-1", value = "1" }, new { key = "set-2", value = "1" } }); // Act Commit(x => x.ExpireSet("set-1", TimeSpan.FromMinutes(60)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var records = sql.Query($"select * from [{Constants.DefaultSchema}].[Set]").ToDictionary(x => (string)x.Key, x => (DateTime?)x.ExpireAt); Assert.True(DateTime.UtcNow.AddMinutes(59) < records["set-1"]); Assert.True(records["set-1"] < DateTime.UtcNow.AddMinutes(61)); Assert.Null(records["set-2"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireList_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.ExpireList(null, TimeSpan.FromSeconds(45)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireList_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => x.InsertToList(TooLongTruncatedKey, "value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.ExpireList(TooLongKey, TimeSpan.FromHours(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [ExpireAt] from [{Constants.DefaultSchema}].[List] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.Null(result.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireList_SetsExpirationTime_OnAList_WithGivenKey(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[List] ([Key]) values (@key)"; UseConnection(sql => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "list-1", value = "1" }, new { key = "list-2", value = "1" } }); // Act Commit(x => x.ExpireList("list-1", TimeSpan.FromMinutes(60)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var records = sql.Query($"select * from [{Constants.DefaultSchema}].[List]").ToDictionary(x => (string)x.Key, x => (DateTime?)x.ExpireAt); Assert.True(DateTime.UtcNow.AddMinutes(59) < records["list-1"]); Assert.True(records["list-1"] < DateTime.UtcNow.AddMinutes(61)); Assert.Null(records["list-2"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistHash_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.PersistHash(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistHash_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => { x.SetRangeInHash(TooLongTruncatedKey, new Dictionary { { "field", "value" } }); x.ExpireHash(TooLongTruncatedKey, TimeSpan.FromHours(1)); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.PersistHash(TooLongKey), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [ExpireAt] from [{Constants.DefaultSchema}].[Hash] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.NotNull(result.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistHash_ClearsExpirationTime_OnAGivenHash(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].Hash ([Key], [Field], [ExpireAt]) values (@key, @field, @expireAt)"; UseConnection(sql => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "hash-1", field = "field", expireAt = DateTime.UtcNow.AddDays(1) }, new { key = "hash-2", field = "field", expireAt = DateTime.UtcNow.AddDays(1) } }); // Act Commit(x => x.PersistHash("hash-1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var records = sql.Query($"select * from [{Constants.DefaultSchema}].Hash").ToDictionary(x => (string)x.Key, x => (DateTime?)x.ExpireAt); Assert.Null(records["hash-1"]); Assert.NotNull(records["hash-2"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistSet_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.PersistSet(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistSet_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => { x.AddToSet(TooLongTruncatedKey, "value"); x.ExpireSet(TooLongTruncatedKey, TimeSpan.FromHours(1)); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.PersistSet(TooLongKey), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [ExpireAt] from [{Constants.DefaultSchema}].[Set] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.NotNull(result.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistSet_ClearsExpirationTime_OnAGivenHash(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[Set] ([Key], [Value], [ExpireAt], [Score]) values (@key, @value, @expireAt, 0.0)"; UseConnection(sql => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "set-1", value = "1", expireAt = DateTime.UtcNow.AddDays(1) }, new { key = "set-2", value = "1", expireAt = DateTime.UtcNow.AddDays(1) } }); // Act Commit(x => x.PersistSet("set-1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var records = sql.Query($"select * from [{Constants.DefaultSchema}].[Set]").ToDictionary(x => (string)x.Key, x => (DateTime?)x.ExpireAt); Assert.Null(records["set-1"]); Assert.NotNull(records["set-2"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistList_ThrowsAnException_WhenKeyIsNull(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var exception = Assert.Throws( () => Commit(x => x.PersistList(null), useMicrosoftDataSqlClient, useBatching, disableTransactionScope)); Assert.Equal("key", exception.ParamName); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistList_DoesNotTruncateKey_BeforeUsingIt(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { // Arrange Commit(x => { x.InsertToList(TooLongTruncatedKey, "value"); x.ExpireList(TooLongTruncatedKey, TimeSpan.FromHours(1)); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Act Commit(x => x.PersistList(TooLongKey), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var result = sql.Query( $"select [ExpireAt] from [{Constants.DefaultSchema}].[List] where [Key] = @key", new { key = TooLongTruncatedKey }).Single(); Assert.NotNull(result.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistList_ClearsExpirationTime_OnAGivenHash(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" insert into [{Constants.DefaultSchema}].[List] ([Key], [ExpireAt]) values (@key, @expireAt)"; UseConnection(sql => { // Arrange sql.Execute(arrangeSql, new[] { new { key = "list-1", expireAt = DateTime.UtcNow.AddDays(1) }, new { key = "list-2", expireAt = DateTime.UtcNow.AddDays(1) } }); // Act Commit(x => x.PersistList("list-1"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); // Assert var records = sql.Query($"select * from [{Constants.DefaultSchema}].[List]").ToDictionary(x => (string)x.Key, x => (DateTime?)x.ExpireAt); Assert.Null(records["list-1"]); Assert.NotNull(records["list-2"]); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void InsertToList_HandlesListIdCanExceedInt32Max(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { sql.Query($"DBCC CHECKIDENT('[{Constants.DefaultSchema}].List', RESEED, {int.MaxValue + 1L});"); Commit(x => x.InsertToList("my-key", "my-value"), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var record = sql.Query($"select * from [{Constants.DefaultSchema}].List").Single(); Assert.True(int.MaxValue < record.Id); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void ExpireJob_SetsJobExpirationData_WhenJobIdIsLongValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, CreatedAt) values (@jobId, '', '', getutcdate())"; UseConnection(sql => { sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L }); Commit(x => x.ExpireJob((int.MaxValue + 1L).ToString(), TimeSpan.FromDays(1)), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, (int.MaxValue + 1L).ToString()); Assert.True(DateTime.UtcNow.AddMinutes(-1) < job.ExpireAt && job.ExpireAt <= DateTime.UtcNow.AddDays(2)); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void PersistJob_ClearsTheJobExpirationData_WhenJobIdIsLongValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, CreatedAt, ExpireAt) values (@jobId, '', '', getutcdate(), getutcdate())"; UseConnection(sql => { sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L }); Commit(x => x.PersistJob((int.MaxValue + 1L).ToString()), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, (int.MaxValue + 1L).ToString()); Assert.Null(job.ExpireAt); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void SetJobState_WorksCorrect_WhenJobIdIsLongValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, CreatedAt) values (@jobId, '', '', getutcdate())"; UseConnection(sql => { sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L }); var state = new Mock(); state.Setup(x => x.Name).Returns("State"); Commit(x => x.SetJobState((int.MaxValue + 1L).ToString(), state.Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var job = GetTestJob(sql, (int.MaxValue + 1L).ToString()); var jobState = sql.Query($"select * from [{Constants.DefaultSchema}].State").Single(); Assert.Equal(int.MaxValue + 1L, jobState.JobId); Assert.Equal(job.StateId, jobState.Id); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AddJobState_AddsAState_WhenJobIdIsLongValue(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { var arrangeSql = $@" SET IDENTITY_INSERT [{Constants.DefaultSchema}].Job ON insert into [{Constants.DefaultSchema}].Job (Id, InvocationData, Arguments, CreatedAt) values (@jobId, '', '', getutcdate())"; UseConnection(sql => { sql.Query( arrangeSql, new { jobId = int.MaxValue + 1L }); var state = new Mock(); state.Setup(x => x.Name).Returns("State"); Commit(x => x.AddJobState((int.MaxValue + 1L).ToString(), state.Object), useMicrosoftDataSqlClient, useBatching, disableTransactionScope); var jobState = sql.Query($"select * from [{Constants.DefaultSchema}].State").Single(); Assert.Equal(int.MaxValue + 1L, jobState.JobId); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AcquireDistributedLock_ThrowsAnException_WhenResourceIsNullOrEmpty(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseSqlServerTransaction(transaction => { var exception = Assert.Throws( () => transaction.AcquireDistributedLock("", TimeSpan.FromSeconds(15))); Assert.Equal("resource", exception.ParamName); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AcquireDistributedLock_AcquiresExclusiveApplicationLock_OnSession(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { using (var sql = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { var options = CreateOptions(useBatching, disableTransactionScope); var storage = new SqlServerStorage(sql, options); using (var connection = new SqlServerConnection(storage)) using (var transaction = new SqlServerWriteOnlyTransaction(connection)) { transaction.AcquireDistributedLock("hello", TimeSpan.FromSeconds(15)); var lockMode = sql.Query( $"select applock_mode('public', '{Constants.DefaultSchema}:hello', 'session')").Single(); Assert.Equal("Exclusive", lockMode); transaction.Commit(); } } } [Theory, CleanDatabase] [InlineData(false)] [InlineData(true)] public void AcquireDistributedLock_ThrowsAnException_IfLockCanNotBeGranted(bool useMicrosoftDataSqlClient) { var releaseLock = new ManualResetEventSlim(false); var lockAcquired = new ManualResetEventSlim(false); var thread = new Thread( () => UseSqlServerTransaction(transaction1 => { try { transaction1.AcquireDistributedLock("exclusive", TimeSpan.Zero); lockAcquired.Set(); if (!releaseLock.Wait(TimeSpan.FromSeconds(30))) throw new TimeoutException("Waiting for too long in transaction1 block"); transaction1.Commit(); } catch (Exception ex) { try { Assert.True(false, ex.ToString()); } catch { // Need only message } } }, useMicrosoftDataSqlClient, useBatching: true, disableTransactionScope: false)); thread.Start(); if (!lockAcquired.Wait(TimeSpan.FromSeconds(30))) { throw new TimeoutException("Waiting for too long in transaction2 block"); } UseSqlServerTransaction(transaction2 => { Assert.Throws( () => transaction2.AcquireDistributedLock("exclusive", TimeSpan.FromSeconds(1))); }, useMicrosoftDataSqlClient, useBatching: true, disableTransactionScope: false); releaseLock.Set(); thread.Join(); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AcquireDistributedLock_TransactionCommit_ReleasesExclusiveApplicationLock(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var options = CreateOptions(useBatching, disableTransactionScope); var storage = new SqlServerStorage(sql, options); using (var connection = new SqlServerConnection(storage)) using (var transaction = new SqlServerWriteOnlyTransaction(connection)) { transaction.AcquireDistributedLock("hello", TimeSpan.FromSeconds(15)); transaction.Commit(); } var lockMode = sql.Query($"select applock_mode('public', '{Constants.DefaultSchema}:hello', 'session')").Single(); Assert.Equal("NoLock", lockMode); }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AcquireDistributedLock_TransactionCommit_DoesNotReleaseLock_IfItsOwnedByConnection(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseConnection(sql => { var options = CreateOptions(useBatching, disableTransactionScope); var storage = new SqlServerStorage(sql, options); using (var connection = new SqlServerConnection(storage)) using (connection.AcquireDistributedLock("hello", TimeSpan.FromSeconds(15))) { using (var transaction = new SqlServerWriteOnlyTransaction(connection)) { transaction.AcquireDistributedLock("hello", TimeSpan.FromSeconds(15)); transaction.Commit(); } var lockMode = sql .Query($"select applock_mode('public', '{Constants.DefaultSchema}:hello', 'session')") .Single(); Assert.Equal("Exclusive", lockMode); } }, useMicrosoftDataSqlClient); } [Theory, CleanDatabase] [MemberData(nameof(GetConfiguration))] public void AcquireDistributedLock_IsReentrant_FromTheSameTransaction_OnTheSameResource(bool useBatching, bool useMicrosoftDataSqlClient, bool disableTransactionScope) { UseSqlServerTransaction(transaction => { transaction.AcquireDistributedLock("hello", TimeSpan.FromSeconds(15)); transaction.AcquireDistributedLock("hello", TimeSpan.FromSeconds(15)); transaction.Commit(); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope); } private static void UseConnection(Action action, bool useMicrosoftDataSqlClient) { using (var connection = ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)) { action(connection); } } private void UseSqlServerConnection( Action action, bool useMicrosoftDataSqlClient, bool useBatching, bool disableTransactionScope, Action optionsAction = null) { var options = CreateOptions(useBatching, disableTransactionScope); optionsAction?.Invoke(options); var storage = new Mock((Func) (() => ConnectionUtils.CreateConnection(useMicrosoftDataSqlClient)), options); storage.Setup(x => x.QueueProviders).Returns(_queueProviders); using (var connection = new SqlServerConnection(storage.Object)) { action(connection); } } private void UseSqlServerTransaction( Action action, bool useMicrosoftDataSqlClient, bool useBatching, bool disableTransactionScope, Action optionsAction = null) { UseSqlServerConnection(connection => { using (var transaction = new SqlServerWriteOnlyTransaction(connection)) { action(transaction); } }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope, optionsAction); } private void Commit( Action action, bool useMicrosoftDataSqlClient, bool useBatching, bool disableTransactionScope, Action optionsAction = null) { UseSqlServerTransaction(transaction => { action(transaction); transaction.Commit(); }, useMicrosoftDataSqlClient, useBatching, disableTransactionScope, optionsAction); } private static SqlServerStorageOptions CreateOptions(bool useBatching, bool disableTransactionScope) { return new SqlServerStorageOptions { #if NET452 || NET461 DisableTransactionScope = disableTransactionScope, #endif CommandBatchMaxTimeout = useBatching ? (TimeSpan?)TimeSpan.FromMinutes(5) : null }; } private static bool IsRunningOnWindows() { return Environment.OSVersion.Platform == PlatformID.Win32NT; } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/StorageOptionsFacts.cs ================================================ using System; using Xunit; namespace Hangfire.SqlServer.Tests { public class StorageOptionsFacts { [Fact] public void Ctor_SetsTheDefaultOptions() { var options = new SqlServerStorageOptions(); Assert.True(options.QueuePollInterval == TimeSpan.Zero); #pragma warning disable 618 Assert.True(options.InvisibilityTimeout > TimeSpan.Zero); #pragma warning restore 618 Assert.True(options.JobExpirationCheckInterval > TimeSpan.Zero); Assert.True(options.PrepareSchemaIfNecessary); } [Fact] public void Set_QueuePollInterval_DoesNotThrow_WhenGivenIntervalIsEqualToZero() { var options = new SqlServerStorageOptions(); options.QueuePollInterval = TimeSpan.Zero; } [Fact] public void Set_QueuePollInterval_ShouldThrowAnException_WhenGivenIntervalIsNegative() { var options = new SqlServerStorageOptions(); Assert.Throws( () => options.QueuePollInterval = TimeSpan.FromSeconds(-1)); } [Fact] public void Set_QueuePollInterval_SetsTheValue() { var options = new SqlServerStorageOptions { QueuePollInterval = TimeSpan.FromSeconds(1) }; Assert.Equal(TimeSpan.FromSeconds(1), options.QueuePollInterval); } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/Utils/CleanDatabaseAttribute.cs ================================================ extern alias ReferencedDapper; using System; using System.Data.SqlClient; using System.Reflection; using System.Threading; using ReferencedDapper::Dapper; using Xunit.Sdk; namespace Hangfire.SqlServer.Tests { public class CleanDatabaseAttribute : BeforeAfterTestAttribute { private static readonly object GlobalLock = new object(); private static bool _sqlObjectInstalled; public CleanDatabaseAttribute() { } public override void Before(MethodInfo methodUnderTest) { Monitor.Enter(GlobalLock); if (!_sqlObjectInstalled) { CreateAndInitializeDatabaseIfNotExists(); _sqlObjectInstalled = true; } using (var connection = new SqlConnection( ConnectionUtils.GetConnectionString())) { connection.Execute(@" truncate table [HangFire].[AggregatedCounter]; truncate table [HangFire].[Counter]; truncate table [HangFire].[Hash]; delete from [HangFire].[Job]; dbcc checkident('HangFire.Job', RESEED, 0); truncate table [HangFire].[List]; truncate table [HangFire].[JobQueue]; truncate table [HangFire].[Set]; truncate table [HangFire].[Server]; "); } } public override void After(MethodInfo methodUnderTest) { Monitor.Exit(GlobalLock); } private static void CreateAndInitializeDatabaseIfNotExists() { var recreateDatabaseSql = String.Format( @"if db_id('{0}') is null create database [{0}] COLLATE SQL_Latin1_General_CP1_CS_AS", ConnectionUtils.GetDatabaseName()); using (var connection = new SqlConnection( ConnectionUtils.GetMasterConnectionString())) { connection.Execute(recreateDatabaseSql); } using (var connection = new SqlConnection( ConnectionUtils.GetConnectionString())) { SqlServerObjectsInstaller.Install(connection, null, true); } } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/Utils/CleanSerializerSettingsAttribute.cs ================================================ using System.Reflection; using Hangfire.Common; using Newtonsoft.Json; using Xunit.Sdk; namespace Hangfire.SqlServer.Tests { internal sealed class CleanSerializerSettingsAttribute : BeforeAfterTestAttribute { public override void Before(MethodInfo methodUnderTest) { ClearSettings(); } public override void After(MethodInfo methodUnderTest) { ClearSettings(); } private static void ClearSettings() { #pragma warning disable 618 JobHelper.SetSerializerSettings(null); #pragma warning restore 618 GlobalConfiguration.Configuration.UseSerializerSettings(null); #if !NET452 && !NET461 JsonConvert.DefaultSettings = null; #endif } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/Utils/ConnectionUtils.cs ================================================ using System; using System.Data.Common; using System.Data.SqlClient; namespace Hangfire.SqlServer.Tests { public static class ConnectionUtils { private const string DatabaseVariable = "Hangfire_SqlServer_DatabaseName"; private const string ConnectionStringTemplateVariable = "Hangfire_SqlServer_ConnectionStringTemplate"; private const string MasterDatabaseName = "master"; private const string DefaultDatabaseName = #if NET452 "Hangfire.SqlServer.Tests.net452" #elif NET461 "Hangfire.SqlServer.Tests.net461" #elif NETCOREAPP3_1 "Hangfire.SqlServer.Tests.netcoreapp3_1" #elif NET6_0 "Hangfire.SqlServer.Tests.net6_0" #elif NET8_0 "Hangfire.SqlServer.Tests.net8_0" #else "Hangfire.SqlServer.Tests" #endif ; private const string DefaultConnectionStringTemplate = @"Server=.\;Database={0};Trusted_Connection=True;TrustServerCertificate=True;"; public static string GetDatabaseName() { return Environment.GetEnvironmentVariable(DatabaseVariable) ?? DefaultDatabaseName; } public static string GetMasterConnectionString() { return String.Format(GetConnectionStringTemplate(), MasterDatabaseName); } public static string GetConnectionString() { return String.Format(GetConnectionStringTemplate(), GetDatabaseName()); } private static string GetConnectionStringTemplate() { return Environment.GetEnvironmentVariable(ConnectionStringTemplateVariable) ?? DefaultConnectionStringTemplate; } public static DbConnection CreateConnection(bool microsoftDataSqlClient) { var connection = #if !NET452 && !NET461 microsoftDataSqlClient ? (DbConnection)new Microsoft.Data.SqlClient.SqlConnection(GetConnectionString()) : #endif new SqlConnection(GetConnectionString()); connection.Open(); return connection; } } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/Utils/SerializerSettingsHelper.cs ================================================ using System.Runtime.Serialization.Formatters; using Newtonsoft.Json; namespace Hangfire.SqlServer.Tests { public static class SerializerSettingsHelper { public static JsonSerializerSettings DangerousSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, #if NET452 || NET461 || NETCOREAPP3_1 TypeNameAssemblyFormat = FormatterAssemblyStyle.Full, #else TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full, #endif DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, }; } } ================================================ FILE: tests/Hangfire.SqlServer.Tests/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.5.2": { "Dapper": { "type": "Direct", "requested": "[1.60.6, )", "resolved": "1.60.6", "contentHash": "mmnJNhKMeF2KhvVXDoVQlFxre8aJAo71YBJrKqFlvuqzYC2QiXUq94/GCDBJzU7paq4GqpkV2glw3308TcGibw==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net452": "1.0.3" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[5.0.1, )", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==" }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==" }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.NETFramework.ReferenceAssemblies.net452": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "kuFOgilYbs29xENHlqQ6aJYa+t56u+OqHx85P7GYLVlo7HL3nsug9IQY2DoPgkOpZ2xb9btYV2EFK7Enll8S3A==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==" }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[1.60.6, )", "Hangfire.Core": "[1.0.0, )" } } }, ".NETFramework,Version=v4.6.1": { "Dapper": { "type": "Direct", "requested": "[1.60.6, )", "resolved": "1.60.6", "contentHash": "mmnJNhKMeF2KhvVXDoVQlFxre8aJAo71YBJrKqFlvuqzYC2QiXUq94/GCDBJzU7paq4GqpkV2glw3308TcGibw==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[5.0.1, )", "resolved": "5.0.1", "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" }, "System.Data.SqlClient": { "type": "Direct", "requested": "[4.5.0, )", "resolved": "4.5.0", "contentHash": "7xeDd8j7T/j2/OWsbVCx1QmCaHf2AnML0FXslTeo4My3F9tAcdSzrnrLdIi49bzNazHykQIfyU5nb3+jmE/CoQ==" }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==" }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==" }, "CronExpressionDescriptor": { "type": "Transitive", "resolved": "1.21.0", "contentHash": "BDusPksr0codp6mgNbXfw8SG/uJKYdflCDkIaLPKD86YIdHPdzgz7hrbWDmlWpkyzJPPZ5uRDQPLaVUJMQIdBQ==" }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.NETFramework.ReferenceAssemblies.net461": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA==" }, "Microsoft.Owin": { "type": "Transitive", "resolved": "4.2.3", "contentHash": "uoOKm7Ouj06+ULS7Ss60tRM2E5t0ku7rQ7cJk864jArtE35WTJKMzUxgHxs7gdiqHZYnC3ddZSr9zj8yRjguEA==", "dependencies": { "Owin": "1.0.0" } }, "Owin": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==" }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "CronExpressionDescriptor": "[1.21.0, )", "Cronos": "[0.11.1, )", "Microsoft.Owin": "[4.2.3, )", "Newtonsoft.Json": "[5.0.1, )", "Owin": "[1.0.0, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[1.60.6, )", "Hangfire.Core": "[1.0.0, )" } } }, "net6.0": { "Dapper": { "type": "Direct", "requested": "[2.1.28, )", "resolved": "2.1.28", "contentHash": "ha49pzOEDmCPkMxwfPSR/wxa/6RD3r42TESIgpzpi7FXq/gNVUuJVEO+wtUzntNRhtmq3BKCl0s0aAlSZLkBUA==" }, "Microsoft.Data.SqlClient": { "type": "Direct", "requested": "[5.2.2, )", "resolved": "5.2.2", "contentHash": "mtoeRMh7F/OA536c/Cnh8L4H0uLSKB5kSmoi54oN7Fp0hNJDy22IqyMhaMH4PkDCqI7xL//Fvg9ldtuPHG0h5g==", "dependencies": { "Azure.Identity": "1.11.4", "Microsoft.Data.SqlClient.SNI.runtime": "5.2.0", "Microsoft.Identity.Client": "4.61.3", "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.35.0", "Microsoft.SqlServer.Server": "1.0.0", "System.Configuration.ConfigurationManager": "6.0.1", "System.Runtime.Caching": "6.0.0" } }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==", "dependencies": { "Microsoft.CodeCoverage": "17.8.0", "Microsoft.TestPlatform.TestHost": "17.8.0" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "NETStandard.Library": "1.6.1", "System.Linq.Queryable": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.3, )", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "System.Data.SqlClient": { "type": "Direct", "requested": "[4.8.6, )", "resolved": "4.8.6", "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==", "dependencies": { "Microsoft.Win32.Registry": "4.7.0", "System.Security.Principal.Windows": "4.7.0", "runtime.native.System.Data.SqlClient.sni": "4.7.0" } }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==", "dependencies": { "Microsoft.NET.Test.Sdk": "15.0.0" } }, "Azure.Core": { "type": "Transitive", "resolved": "1.38.0", "contentHash": "IuEgCoVA0ef7E4pQtpC3+TkPbzaoQfa77HlfJDmfuaJUCVJmn7fT0izamZiryW5sYUFKizsftIxMkXKbgIcPMQ==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.ClientModel": "1.0.0", "System.Diagnostics.DiagnosticSource": "6.0.1", "System.Memory.Data": "1.0.2", "System.Numerics.Vectors": "4.5.0", "System.Text.Encodings.Web": "4.7.2", "System.Text.Json": "4.7.2", "System.Threading.Tasks.Extensions": "4.5.4" } }, "Azure.Identity": { "type": "Transitive", "resolved": "1.11.4", "contentHash": "Sf4BoE6Q3jTgFkgBkx7qztYOFELBCo+wQgpYDwal/qJ1unBH73ywPztIJKXBXORRzAeNijsuxhk94h0TIMvfYg==", "dependencies": { "Azure.Core": "1.38.0", "Microsoft.Identity.Client": "4.61.3", "Microsoft.Identity.Client.Extensions.Msal": "4.61.3", "System.Memory": "4.5.4", "System.Security.Cryptography.ProtectedData": "4.7.0", "System.Text.Json": "4.7.2", "System.Threading.Tasks.Extensions": "4.5.4" } }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==", "dependencies": { "NETStandard.Library": "1.6.1", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.TypeConverter": "4.3.0", "System.Diagnostics.TraceSource": "4.3.0", "System.Dynamic.Runtime": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Xml.XmlDocument": "4.3.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "1.1.1", "contentHash": "yuvf07qFWFqtK3P/MRkEKLhn5r2UbSpVueRziSqj0yJQIKFwG1pq9mOayK3zE5qZCTs0CbrwL9M6R8VwqyGy2w==" }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "kaj6Wb4qoMuH3HySFJhxwQfe8R/sJsNJnANrvv8WdFPMoNbKY5htfNscv+LHCu5ipz+49m2e+WQXpLXr9XYemQ==" }, "Microsoft.Data.SqlClient.SNI.runtime": { "type": "Transitive", "resolved": "5.2.0", "contentHash": "po1jhvFd+8pbfvJR/puh+fkHi0GRanAdvayh/0e47yaM6CXWZ6opUjCMFuYlAnD2LcbyvQE7fPJKvogmaUcN+w==" }, "Microsoft.Identity.Client": { "type": "Transitive", "resolved": "4.61.3", "contentHash": "naJo/Qm35Caaoxp5utcw+R8eU8ZtLz2ALh8S+gkekOYQ1oazfCQMWVT4NJ/FnHzdIJlm8dMz0oMpMGCabx5odA==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "6.35.0", "System.Diagnostics.DiagnosticSource": "6.0.1" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", "resolved": "4.61.3", "contentHash": "PWnJcznrSGr25MN8ajlc2XIDW4zCFu0U6FkpaNLEWLgd1NgFCp5uDY3mqLDgM8zCN8hqj8yo5wHYfLB2HjcdGw==", "dependencies": { "Microsoft.Identity.Client": "4.61.3", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, "Microsoft.IdentityModel.Abstractions": { "type": "Transitive", "resolved": "6.35.0", "contentHash": "xuR8E4Rd96M41CnUSCiOJ2DBh+z+zQSmyrYHdYhD6K4fXBcQGVnRCFQ0efROUYpP+p0zC1BLKr0JRpVuujTZSg==" }, "Microsoft.IdentityModel.JsonWebTokens": { "type": "Transitive", "resolved": "6.35.0", "contentHash": "9wxai3hKgZUb4/NjdRKfQd0QJvtXKDlvmGMYACbEC8DFaicMFCFhQFZq9ZET1kJLwZahf2lfY5Gtcpsx8zYzbg==", "dependencies": { "Microsoft.IdentityModel.Tokens": "6.35.0", "System.Text.Encoding": "4.3.0", "System.Text.Encodings.Web": "4.7.2", "System.Text.Json": "4.7.2" } }, "Microsoft.IdentityModel.Logging": { "type": "Transitive", "resolved": "6.35.0", "contentHash": "jePrSfGAmqT81JDCNSY+fxVWoGuJKt9e6eJ+vT7+quVS55nWl//jGjUQn4eFtVKt4rt5dXaleZdHRB9J9AJZ7Q==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "6.35.0" } }, "Microsoft.IdentityModel.Protocols": { "type": "Transitive", "resolved": "6.35.0", "contentHash": "BPQhlDzdFvv1PzaUxNSk+VEPwezlDEVADIKmyxubw7IiELK18uJ06RQ9QKKkds30XI+gDu9n8j24XQ8w7fjWcg==", "dependencies": { "Microsoft.IdentityModel.Logging": "6.35.0", "Microsoft.IdentityModel.Tokens": "6.35.0" } }, "Microsoft.IdentityModel.Protocols.OpenIdConnect": { "type": "Transitive", "resolved": "6.35.0", "contentHash": "LMtVqnECCCdSmyFoCOxIE5tXQqkOLrvGrL7OxHg41DIm1bpWtaCdGyVcTAfOQpJXvzND9zUKIN/lhngPkYR8vg==", "dependencies": { "Microsoft.IdentityModel.Protocols": "6.35.0", "System.IdentityModel.Tokens.Jwt": "6.35.0" } }, "Microsoft.IdentityModel.Tokens": { "type": "Transitive", "resolved": "6.35.0", "contentHash": "RN7lvp7s3Boucg1NaNAbqDbxtlLj5Qeb+4uSS1TeK5FSBVM40P4DKaTKChT43sHyKfh7V0zkrMph6DdHvyA4bg==", "dependencies": { "Microsoft.CSharp": "4.5.0", "Microsoft.IdentityModel.Logging": "6.35.0", "System.Security.Cryptography.Cng": "4.5.0" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.SqlServer.Server": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==" }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "AYy6vlpGMfz5kOFq99L93RGbqftW/8eQTqjT9iGXW6s9MRP3UdtY8idJ8rJcjeSja8A18IhIro5YnH3uv1nz4g==", "dependencies": { "NuGet.Frameworks": "6.5.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "9ivcl/7SGRmOT0YYrHQGohWiT5YCpkmy/UEzldfVisLm6QxbLaK3FAJqZXI34rnRLmqqDCeMQxKINwmKwAPiDw==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "17.8.0", "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { "System.Security.AccessControl": "4.7.0", "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, "NETStandard.Library": { "type": "Transitive", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "6.5.0", "contentHash": "QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg==" }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "9kyFSIdN3T0qjDQ2R0HRXYIhS3l5psBzQi6qqhdLz+SzFyEy4sVxNOke+yyYv8Cu8rPER12c3RDjLT8wF3WBYQ==", "dependencies": { "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", "dependencies": { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" }, "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" }, "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.ClientModel": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "I3CVkvxeqFYjIVEP59DnjbeoGNfo/+SZrCLpRz2v/g0gpCHaEMPtWSY0s9k/7jR1rAsLNg2z2u1JRB76tPjnIw==", "dependencies": { "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Collections.NonGeneric": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections.Specialized": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", "dependencies": { "System.Collections.NonGeneric": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.ComponentModel.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", "dependencies": { "System.ComponentModel": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.ComponentModel.TypeConverter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", "dependencies": { "System.Collections": "4.3.0", "System.Collections.NonGeneric": "4.3.0", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.Primitives": "4.3.0", "System.Globalization": "4.3.0", "System.Linq": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.1", "contentHash": "jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==", "dependencies": { "System.Security.Cryptography.ProtectedData": "6.0.0", "System.Security.Permissions": "6.0.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "6.0.1", "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.TraceSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Drawing.Common": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", "dependencies": { "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0" } }, "System.IdentityModel.Tokens.Jwt": { "type": "Transitive", "resolved": "6.35.0", "contentHash": "yxGIQd3BFK7F6S62/7RdZk3C/mfwyVxvh6ngd1VYMBmbJ1YZZA9+Ku6suylVtso0FjI0wbElpJ0d27CdsyLpBQ==", "dependencies": { "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", "Microsoft.IdentityModel.Tokens": "6.35.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Linq": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Emit.Lightweight": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Linq.Queryable": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "In1Bmmvl/j52yPu3xgakQSI0YIckPUr870w4K5+Lak3JCCa8hl+my65lABOuKfYs4ugmZy25ScFerC4nz8+b6g==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Memory": { "type": "Transitive", "resolved": "4.5.4", "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==" }, "System.Memory.Data": { "type": "Transitive", "resolved": "1.0.2", "contentHash": "JGkzeqgBsiZwKJZ1IxPNsDFZDhUvuEdX8L8BDC8N3KOj+6zMcNU28CNN59TpZE/VJYy9cP+5M+sbxtWJx3/xtw==", "dependencies": { "System.Text.Encodings.Web": "4.7.2", "System.Text.Json": "4.6.0" } }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", "dependencies": { "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.6.0", "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.Caching": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "E0e03kUp5X2k+UAoVl6efmI7uU7JRBWi5EIdlQ7cr0NpBGjHG4fWII35PgsBY9T4fJQ8E4QPsL0rKksU9gcL5A==", "dependencies": { "System.Configuration.ConfigurationManager": "6.0.0" } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.Apple": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "WG3r7EyjUe9CMPFSs6bty5doUqT+q9pbI80hlNzo2SkPkZ4VTuZkGWjpp77JB8+uaL4DFPRdBsAY+DX3dBK92A==" }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", "dependencies": { "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Cng": "4.3.0", "System.Security.Cryptography.Csp": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", "dependencies": { "System.Security.AccessControl": "6.0.0", "System.Windows.Extensions": "6.0.0" } }, "System.Security.Principal.Windows": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "4.7.2", "contentHash": "iTUgB/WtrZ1sWZs84F2hwyQhiRH6QNjQv2DkwrH+WP6RoFga2Q1m3f9/Q7FG8cck8AdHitQkmkXSY8qylcDmuA==" }, "System.Text.Json": { "type": "Transitive", "resolved": "4.7.2", "contentHash": "TcMd95wcrubm9nHvJEQs70rC0H/8omiSGGpU4FQ/ZA1URIqD4pjmFJh2Mfv1yH1eHgJDWTi2hMDXwTET+zOOyg==" }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.5.4", "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", "dependencies": { "System.Drawing.Common": "6.0.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[2.1.28, )", "Hangfire.Core": "[1.0.0, )" } } }, "net8.0": { "Dapper": { "type": "Direct", "requested": "[2.1.28, )", "resolved": "2.1.28", "contentHash": "ha49pzOEDmCPkMxwfPSR/wxa/6RD3r42TESIgpzpi7FXq/gNVUuJVEO+wtUzntNRhtmq3BKCl0s0aAlSZLkBUA==" }, "Microsoft.Data.SqlClient": { "type": "Direct", "requested": "[6.1.1, )", "resolved": "6.1.1", "contentHash": "syGQmIUPAYYHAHyTD8FCkTNThpQWvoA7crnIQRMfp8dyB5A2cWU3fQexlRTFkVmV7S0TjVmthi0LJEFVjHo8AQ==", "dependencies": { "Azure.Core": "1.47.1", "Azure.Identity": "1.14.2", "Microsoft.Bcl.Cryptography": "8.0.0", "Microsoft.Data.SqlClient.SNI.runtime": "6.0.2", "Microsoft.Extensions.Caching.Memory": "8.0.1", "Microsoft.IdentityModel.JsonWebTokens": "7.7.1", "Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.7.1", "Microsoft.SqlServer.Server": "1.0.0", "System.Configuration.ConfigurationManager": "8.0.1", "System.Security.Cryptography.Pkcs": "8.0.1", "System.Text.Json": "8.0.5" } }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.8.0, )", "resolved": "17.8.0", "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==", "dependencies": { "Microsoft.CodeCoverage": "17.8.0", "Microsoft.TestPlatform.TestHost": "17.8.0" } }, "Moq": { "type": "Direct", "requested": "[4.10.0, )", "resolved": "4.10.0", "contentHash": "YZ1yTTDkFdgjglo9v2Gy4jnWUUIPTQETCul9CDguydLYrVgQXr6L1n3CEJqy/S9kgX+Er0cRQixky2grMwtvxA==", "dependencies": { "Castle.Core": "4.3.1", "NETStandard.Library": "1.6.1", "System.Linq.Queryable": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0", "System.ValueTuple": "4.4.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.3, )", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "System.Data.SqlClient": { "type": "Direct", "requested": "[4.9.0, )", "resolved": "4.9.0", "contentHash": "j4KJO+vC62NyUtNHz854njEqXbT8OmAa5jb1nrGfYWBOcggyYUQE0w/snXeaCjdvkSKWuUD+hfvlbN8pTrJTXg==", "dependencies": { "runtime.native.System.Data.SqlClient.sni": "4.4.0" } }, "xunit": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "NL00nGsDsyWc1CWxz5FXXjLpW9oFG18WJoTPCyhNv4KGP/e5iLJqAqgM1uaJZyQ6WaTtmWIy4yjYP3RdcaT7Vw==", "dependencies": { "xunit.analyzers": "0.10.0", "xunit.assert": "[2.4.0]", "xunit.core": "[2.4.0]" } }, "xunit.runner.visualstudio": { "type": "Direct", "requested": "[2.4.0, )", "resolved": "2.4.0", "contentHash": "3eq5cGXbEJkqW9nwLuXwtxy9B5gMA8i7HW4rN63AhAvy5UvEcQbZnve23wx/oPrkyg/4CbfNhxkBezS0b1oUdQ==", "dependencies": { "Microsoft.NET.Test.Sdk": "15.0.0" } }, "Azure.Core": { "type": "Transitive", "resolved": "1.47.1", "contentHash": "oPcncSsDHuxB8SC522z47xbp2+ttkcKv2YZ90KXhRKN0YQd2+7l1UURT9EBzUNEXtkLZUOAB5xbByMTrYRh3yA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "8.0.0", "System.ClientModel": "1.5.1", "System.Memory.Data": "8.0.1" } }, "Azure.Identity": { "type": "Transitive", "resolved": "1.14.2", "contentHash": "YhNMwOTwT+I2wIcJKSdP0ADyB2aK+JaYWZxO8LSRDm5w77LFr0ykR9xmt2ZV5T1gaI7xU6iNFIh/yW1dAlpddQ==", "dependencies": { "Azure.Core": "1.46.1", "Microsoft.Identity.Client": "4.73.1", "Microsoft.Identity.Client.Extensions.Msal": "4.73.1", "System.Memory": "4.5.5" } }, "Castle.Core": { "type": "Transitive", "resolved": "4.3.1", "contentHash": "8Y/eTr6GTElAGV7eAmJuhfLhGdFpNvaNrQ9UQYDScziLmX+/BLGM+9eQr0IcdNDcPN0ADmbtwT6MgecGKy4obw==", "dependencies": { "NETStandard.Library": "1.6.1", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.TypeConverter": "4.3.0", "System.Diagnostics.TraceSource": "4.3.0", "System.Dynamic.Runtime": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Xml.XmlDocument": "4.3.0" } }, "Cronos": { "type": "Transitive", "resolved": "0.11.1", "contentHash": "5Ug+giPQITSAdTp/METAsofRSSUi3I5p7t4dlcXnzUgUzwZb4HkOBcYfpHuPwAHrnKJjmyW8amVzLD6mfLpaBg==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==" }, "Microsoft.Bcl.Cryptography": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "Y3t/c7C5XHJGFDnohjf1/9SYF3ZOfEU1fkNQuKg/dGf9hN18yrQj2owHITGfNS3+lKJdW6J4vY98jYu57jCO8A==" }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==" }, "Microsoft.Data.SqlClient.SNI.runtime": { "type": "Transitive", "resolved": "6.0.2", "contentHash": "f+pRODTWX7Y67jXO3T5S2dIPZ9qMJNySjlZT/TKmWVNWe19N8jcWmHaqHnnchaq3gxEKv1SWVY5EFzOD06l41w==" }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==", "dependencies": { "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Caching.Memory": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "HFDnhYLccngrzyGgHkjEDU5FMLn4MpOsr5ElgsBMC4yx6lJh4jeWO7fHS8+TXPq+dgxCmUa/Trl8svObmwW4QA==", "dependencies": { "Microsoft.Extensions.Caching.Abstractions": "8.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", "Microsoft.Extensions.Logging.Abstractions": "8.0.2", "Microsoft.Extensions.Options": "8.0.2", "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "8.0.2", "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "8.0.3", "contentHash": "dL0QGToTxggRLMYY4ZYX5AMwBb+byQBd/5dMiZE07Nv73o6I5Are3C7eQTh7K2+A4ct0PVISSr7TZANbiNb2yQ==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "8.0.2", "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" }, "Microsoft.Identity.Client": { "type": "Transitive", "resolved": "4.73.1", "contentHash": "NnDLS8QwYqO5ZZecL2oioi1LUqjh5Ewk4bMLzbgiXJbQmZhDLtKwLxL3DpGMlQAJ2G4KgEnvGPKa+OOgffeJbw==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "6.35.0", "System.Diagnostics.DiagnosticSource": "6.0.1" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", "resolved": "4.73.1", "contentHash": "xDztAiV2F0wI0W8FLKv5cbaBefyLD6JVaAsvgSN7bjWNCzGYzHbcOEIP5s4TJXUpQzMfUyBsFl1mC6Zmgpz0PQ==", "dependencies": { "Microsoft.Identity.Client": "4.73.1", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, "Microsoft.IdentityModel.Abstractions": { "type": "Transitive", "resolved": "7.7.1", "contentHash": "S7sHg6gLg7oFqNGLwN1qSbJDI+QcRRj8SuJ1jHyCmKSipnF6ZQL+tFV2NzVfGj/xmGT9TykQdQiBN+p5Idl4TA==" }, "Microsoft.IdentityModel.JsonWebTokens": { "type": "Transitive", "resolved": "7.7.1", "contentHash": "3Izi75UCUssvo8LPx3OVnEeZay58qaFicrtSnbtUt7q8qQi0gy46gh4V8VUTkMVMKXV6VMyjBVmeNNgeCUJuIw==", "dependencies": { "Microsoft.IdentityModel.Tokens": "7.7.1" } }, "Microsoft.IdentityModel.Logging": { "type": "Transitive", "resolved": "7.7.1", "contentHash": "BZNgSq/o8gsKExdYoBKPR65fdsxW0cTF8PsdqB8y011AGUJJW300S/ZIsEUD0+sOmGc003Gwv3FYbjrVjvsLNQ==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "7.7.1" } }, "Microsoft.IdentityModel.Protocols": { "type": "Transitive", "resolved": "7.7.1", "contentHash": "h+fHHBGokepmCX+QZXJk4Ij8OApCb2n2ktoDkNX5CXteXsOxTHMNgjPGpAwdJMFvAL7TtGarUnk3o97NmBq2QQ==", "dependencies": { "Microsoft.IdentityModel.Tokens": "7.7.1" } }, "Microsoft.IdentityModel.Protocols.OpenIdConnect": { "type": "Transitive", "resolved": "7.7.1", "contentHash": "yT2Hdj8LpPbcT9C9KlLVxXl09C8zjFaVSaApdOwuecMuoV4s6Sof/mnTDz/+F/lILPIBvrWugR9CC7iRVZgbfQ==", "dependencies": { "Microsoft.IdentityModel.Protocols": "7.7.1", "System.IdentityModel.Tokens.Jwt": "7.7.1" } }, "Microsoft.IdentityModel.Tokens": { "type": "Transitive", "resolved": "7.7.1", "contentHash": "fQ0VVCba75lknUHGldi3iTKAYUQqbzp1Un8+d9cm9nON0Gs8NAkXddNg8iaUB0qi/ybtAmNWizTR4avdkCJ9pQ==", "dependencies": { "Microsoft.IdentityModel.Logging": "7.7.1" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" }, "Microsoft.SqlServer.Server": { "type": "Transitive", "resolved": "1.0.0", "contentHash": "N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==" }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "AYy6vlpGMfz5kOFq99L93RGbqftW/8eQTqjT9iGXW6s9MRP3UdtY8idJ8rJcjeSja8A18IhIro5YnH3uv1nz4g==", "dependencies": { "NuGet.Frameworks": "6.5.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "17.8.0", "contentHash": "9ivcl/7SGRmOT0YYrHQGohWiT5YCpkmy/UEzldfVisLm6QxbLaK3FAJqZXI34rnRLmqqDCeMQxKINwmKwAPiDw==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "17.8.0", "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "NETStandard.Library": { "type": "Transitive", "resolved": "1.6.1", "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.Win32.Primitives": "4.3.0", "System.AppContext": "4.3.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Console": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.Compression.ZipFile": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Net.Http": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Timer": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XDocument": "4.3.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "6.5.0", "contentHash": "QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg==" }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" }, "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" }, "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==", "dependencies": { "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" } }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", "dependencies": { "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" } }, "runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", "dependencies": { "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" }, "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" }, "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" }, "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" }, "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" }, "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" }, "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" }, "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" }, "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Buffers": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.ClientModel": { "type": "Transitive", "resolved": "1.5.1", "contentHash": "k2jKSO0X45IqhVOT9iQB4xralNN9foRQsRvXBTyRpAVxyzCJlG895T9qYrQWbcJ6OQXxOouJQ37x5nZH5XKK+A==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.3", "System.Memory.Data": "8.0.1" } }, "System.Collections": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Collections.NonGeneric": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Collections.Specialized": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", "dependencies": { "System.Collections.NonGeneric": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.ComponentModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.ComponentModel.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", "dependencies": { "System.ComponentModel": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.ComponentModel.TypeConverter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", "dependencies": { "System.Collections": "4.3.0", "System.Collections.NonGeneric": "4.3.0", "System.Collections.Specialized": "4.3.0", "System.ComponentModel": "4.3.0", "System.ComponentModel.Primitives": "4.3.0", "System.Globalization": "4.3.0", "System.Linq": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "gPYFPDyohW2gXNhdQRSjtmeS6FymL2crg4Sral1wtvEJ7DUqFCDWDVbbLobASbzxfic8U1hQEdC7hmg9LHncMw==", "dependencies": { "System.Diagnostics.EventLog": "8.0.1", "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "6.0.1", "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "n1ZP7NM2Gkn/MgD8+eOT5MulMj6wfeQMNS2Pizvq5GHCZfjlFMXV2irQlQmJhwA2VABC57M0auudO89Iu2uRLg==" }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Diagnostics.TraceSource": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Globalization.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0" } }, "System.IdentityModel.Tokens.Jwt": { "type": "Transitive", "resolved": "7.7.1", "contentHash": "rQkO1YbAjLwnDJSMpRhRtrc6XwIcEOcUvoEcge+evurpzSZM3UNK+MZfD3sKyTlYsvknZ6eJjSBfnmXqwOsT9Q==", "dependencies": { "Microsoft.IdentityModel.JsonWebTokens": "7.7.1", "Microsoft.IdentityModel.Tokens": "7.7.1" } }, "System.IO": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Buffers": "4.3.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.IO.Compression": "4.3.0" } }, "System.IO.Compression.ZipFile": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", "dependencies": { "System.Buffers": "4.3.0", "System.IO": "4.3.0", "System.IO.Compression": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Linq": "4.3.0", "System.ObjectModel": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Emit.Lightweight": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Reflection.TypeExtensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Threading": "4.3.0" } }, "System.Linq.Queryable": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "In1Bmmvl/j52yPu3xgakQSI0YIckPUr870w4K5+Lak3JCCa8hl+my65lABOuKfYs4ugmZy25ScFerC4nz8+b6g==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Linq": "4.3.0", "System.Linq.Expressions": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Memory": { "type": "Transitive", "resolved": "4.5.5", "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" }, "System.Memory.Data": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "BVYuec3jV23EMRDeR7Dr1/qhx7369dZzJ9IWy2xylvb4YfXsrUxspWc4UWYid/tj4zZK58uGZqn2WQiaDMhmAg==" }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.DiagnosticSource": "4.3.0", "System.Diagnostics.Tracing": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Extensions": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Net.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Net.Sockets": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Net.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.IO": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", "dependencies": { "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Emit.ILGeneration": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.6.0", "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", "dependencies": { "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Globalization": "4.3.0", "System.Reflection": "4.3.0", "System.Runtime": "4.3.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0" } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Reflection": "4.3.0", "System.Reflection.Primitives": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Handles": "4.3.0" } }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", "dependencies": { "System.Reflection": "4.3.0", "System.Reflection.Extensions": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", "dependencies": { "System.Globalization": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.Apple": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Collections.Concurrent": "4.3.0", "System.Linq": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", "dependencies": { "System.Collections": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA==" }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", "dependencies": { "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Threading": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.Globalization.Calendars": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.Handles": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Runtime.Numerics": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Cryptography.Cng": "4.3.0", "System.Security.Cryptography.Csp": "4.3.0", "System.Security.Cryptography.Encoding": "4.3.0", "System.Security.Cryptography.OpenSsl": "4.3.0", "System.Security.Cryptography.Primitives": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "runtime.native.System": "4.3.0", "runtime.native.System.Net.Http": "4.3.0", "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0", "System.Text.Encoding": "4.3.0" } }, "System.Text.Json": { "type": "Transitive", "resolved": "8.0.5", "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", "dependencies": { "System.Runtime": "4.3.0" } }, "System.Threading": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", "dependencies": { "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", "dependencies": { "System.Collections": "4.3.0", "System.Runtime": "4.3.0", "System.Threading.Tasks": "4.3.0" } }, "System.Threading.Timer": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0", "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.4.0", "contentHash": "BahUww/+mdP4ARCAh2RQhQTg13wYLVrBb9SYVgW8ZlrwjraGCXHGjo0oIiUfZ34LUZkMMR+RAzR7dEY4S1HeQQ==" }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.IO.FileSystem": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Runtime.InteropServices": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Text.Encoding.Extensions": "4.3.0", "System.Text.RegularExpressions": "4.3.0", "System.Threading.Tasks": "4.3.0", "System.Threading.Tasks.Extensions": "4.3.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Diagnostics.Tools": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Reflection": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.3.0", "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", "dependencies": { "System.Collections": "4.3.0", "System.Diagnostics.Debug": "4.3.0", "System.Globalization": "4.3.0", "System.IO": "4.3.0", "System.Resources.ResourceManager": "4.3.0", "System.Runtime": "4.3.0", "System.Runtime.Extensions": "4.3.0", "System.Text.Encoding": "4.3.0", "System.Threading": "4.3.0", "System.Xml.ReaderWriter": "4.3.0" } }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.2", "contentHash": "vItLB0WkaKg0426RgWq+ZdXH6D+YV/uH28C0weWMOBnVx7I+luHuEYss9hoOngpkiN5kUpLvh9VZRx1H2sk59A==" }, "xunit.analyzers": { "type": "Transitive", "resolved": "0.10.0", "contentHash": "4/IDFCJfIeg6bix9apmUtIMwvOsiwqdEexeO/R2D4GReIGPLIRODTpId/l4LRSrAJk9lEO3Zx1H0Zx6uohJDNg==" }, "xunit.assert": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "Swvkm6iTjZr8TiUj5vMnmfG+2dD4s/BIBgsVOzTxxmoq2ndGsmM2WIL4wuqJ8RhxydWIDOPpIaaytjT2pMTEdg==" }, "xunit.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "BJ/O/tPEcHUCwQYuwqXoYccTMyw6B5dA6yh7WxWWBhKbjqTsG9RWL0nCQXM5yQYJwUuFzBkiXDPN1BO6UdBB4Q==", "dependencies": { "xunit.extensibility.core": "[2.4.0]", "xunit.extensibility.execution": "[2.4.0]" } }, "xunit.extensibility.core": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "qr/KrR6uukHXD9e/lLQjyCPfMEDuvvhNFDzsYzCF2kKlYKiqcADfUvA9Q68rBtKFtwHFeghjWEuv15KoGD2SfA==", "dependencies": { "xunit.abstractions": "2.0.2" } }, "xunit.extensibility.execution": { "type": "Transitive", "resolved": "2.4.0", "contentHash": "252Dzn7i5bMPKtAL15aOP3qJhxKd+57I8ldwIQRJa745JxQuiBu5Da0vtIISVTtc3buRSkBwVnD9iUzsEmCzZA==", "dependencies": { "xunit.extensibility.core": "[2.4.0]" } }, "hangfire.core": { "type": "Project", "dependencies": { "Cronos": "[0.11.1, )", "Microsoft.CSharp": "[4.4.0, )", "Newtonsoft.Json": "[11.0.1, )" } }, "hangfire.sqlserver": { "type": "Project", "dependencies": { "Dapper": "[2.1.28, )", "Hangfire.Core": "[1.0.0, )" } } } } }