├── .gitignore ├── .nuget ├── NuGet.exe ├── packages.config └── packages.lock.json ├── .vs └── HangFire.Azure.ServiceBusQueue │ └── DesignTimeBuild │ └── .dtbcache.v2 ├── Directory.Build.props ├── HangFire.Azure.ServiceBusQueue.sln ├── NuGet.config ├── appveyor.yml ├── build.bat ├── icon.png ├── license.txt ├── nuspecs └── Hangfire.Azure.ServiceBusQueue.nuspec ├── psake-project.ps1 ├── readme.md ├── src ├── Directory.Build.props ├── HangFire.Azure.ServiceBusQueue │ ├── AsyncHelper.cs │ ├── HangFire.Azure.ServiceBusQueue.csproj │ ├── IRetryPolicy.cs │ ├── LinearRetryPolicy.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── QueueDescription.cs │ ├── ServiceBusGlobalConfigurationExtensions.cs │ ├── ServiceBusManager.cs │ ├── ServiceBusQueueFetchedJob.cs │ ├── ServiceBusQueueJobQueue.cs │ ├── ServiceBusQueueJobQueueProvider.cs │ ├── ServiceBusQueueMonitoringApi.cs │ ├── ServiceBusQueueOptions.cs │ ├── ServiceBusQueueSqlServerStorageExtensions.cs │ └── packages.lock.json └── SharedAssemblyInfo.cs └── tests ├── Directory.Build.props └── HangFire.Azure.ServiceBusQueue.Tests ├── HangFire.Azure.ServiceBusQueue.Tests.csproj ├── ServiceBusQueueJobQueueFacts.cs ├── ServiceBusQueueMonitoringApiFacts.cs ├── TestServiceBusQueueOptions.cs ├── appsettings.json └── packages.lock.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # MSTest test Results 20 | [Tt]est[Rr]esult*/ 21 | [Bb]uild[Ll]og.* 22 | 23 | #NUNIT 24 | *.VisualState.xml 25 | TestResult.xml 26 | 27 | # Build Results of an ATL Project 28 | [Dd]ebugPS/ 29 | [Rr]eleasePS/ 30 | dlldata.c 31 | 32 | *_i.c 33 | *_p.c 34 | *_i.h 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.svclog 55 | *.scc 56 | 57 | # Chutzpah Test files 58 | _Chutzpah* 59 | 60 | # Visual C++ cache files 61 | ipch/ 62 | *.aps 63 | *.ncb 64 | *.opensdf 65 | *.sdf 66 | *.cachefile 67 | 68 | # Visual Studio profiler 69 | *.psess 70 | *.vsp 71 | *.vspx 72 | 73 | # TFS 2012 Local Workspace 74 | $tf/ 75 | 76 | # Guidance Automation Toolkit 77 | *.gpState 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # JustCode is a .NET coding addin-in 85 | .JustCode 86 | 87 | # TeamCity is a build add-in 88 | _TeamCity* 89 | 90 | # DotCover is a Code Coverage Tool 91 | *.dotCover 92 | 93 | # NCrunch 94 | *.ncrunch* 95 | _NCrunch_* 96 | .*crunch*.local.xml 97 | 98 | # MightyMoose 99 | *.mm.* 100 | AutoTest.Net/ 101 | 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | 105 | # Installshield output folder 106 | [Ee]xpress/ 107 | 108 | # DocProject is a documentation generator add-in 109 | DocProject/buildhelp/ 110 | DocProject/Help/*.HxT 111 | DocProject/Help/*.HxC 112 | DocProject/Help/*.hhc 113 | DocProject/Help/*.hhk 114 | DocProject/Help/*.hhp 115 | DocProject/Help/Html2 116 | DocProject/Help/html 117 | 118 | # Click-Once directory 119 | publish/ 120 | 121 | # Publish Web Output 122 | *.[Pp]ublish.xml 123 | *.azurePubxml 124 | 125 | # NuGet Packages Directory 126 | packages/ 127 | ## TODO: If the tool you use requires repositories.config uncomment the next line 128 | #!packages/repositories.config 129 | 130 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 131 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 132 | !packages/build/ 133 | 134 | # Windows Azure Build Output 135 | csx/ 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.dbproj.schemaview 150 | *.pfx 151 | *.publishsettings 152 | node_modules/ 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | *.mdf 166 | *.ldf 167 | 168 | # Business Intelligence projects 169 | *.rdl.data 170 | *.bim.layout 171 | *.bim_*.settings 172 | 173 | # Microsoft Fakes 174 | FakesAssemblies/ -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HangfireIO/Hangfire.Azure.ServiceBusQueue/a28ce318fc984642fc5d764d90075abedafb448d/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.nuget/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "Any,Version=v0.0": { 5 | "Hangfire.Build": { 6 | "type": "Direct", 7 | "requested": "[0.5.0, 0.5.0]", 8 | "resolved": "0.5.0", 9 | "contentHash": "4yRCdMaDr6cyFRmCvpFO8kBMV57KPOofugaHOsjkDEDw+G/BCGWOdrpXfkAeTEtZBPUv2jS0PYmVNK5680KxXQ==" 10 | }, 11 | "psake": { 12 | "type": "Direct", 13 | "requested": "[4.4.1, 4.4.1]", 14 | "resolved": "4.4.1", 15 | "contentHash": "Hn5kdGPEoapi+wAAjaGjKEZVnuYp7fUrPK3IivLYG6Bn4adhd8l+KXXPMEmte41RmrLvfV7XGZa9KsSTc0gjDA==" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /.vs/HangFire.Azure.ServiceBusQueue/DesignTimeBuild/.dtbcache.v2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HangfireIO/Hangfire.Azure.ServiceBusQueue/a28ce318fc984642fc5d764d90075abedafb448d/.vs/HangFire.Azure.ServiceBusQueue/DesignTimeBuild/.dtbcache.v2 -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | 6 | 7 | 8 | true 9 | 10 | -------------------------------------------------------------------------------- /HangFire.Azure.ServiceBusQueue.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29123.88 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangfire.Azure.ServiceBusQueue", "src\HangFire.Azure.ServiceBusQueue\Hangfire.Azure.ServiceBusQueue.csproj", "{4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HangFire.Azure.ServiceBusQueue.Tests", "tests\HangFire.Azure.ServiceBusQueue.Tests\HangFire.Azure.ServiceBusQueue.Tests.csproj", "{C9D29F07-4445-4FCB-BC38-2221B2B38231}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {4CC51F69-0311-4485-B7DE-9ECAB3A1B5E5}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {C9D29F07-4445-4FCB-BC38-2221B2B38231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {C9D29F07-4445-4FCB-BC38-2221B2B38231}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {C9D29F07-4445-4FCB-BC38-2221B2B38231}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {C9D29F07-4445-4FCB-BC38-2221B2B38231}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Microsoft;aspnet;HangfireIO;odinserj;psake;jamesnk;charliepoole 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor CI build file, https://ci.appveyor.com/project/odinserj/hangfire 2 | 3 | # Notes: 4 | # - Minimal appveyor.yml file is an empty file. All sections are optional. 5 | # - Indent each level of configuration with 2 spaces. Do not use tabs! 6 | # - All section names are case-sensitive. 7 | # - Section names should be unique on each level. 8 | 9 | # Don't edit manually! Use `build.bat version` command instead! 10 | version: 6.0.0-build-0{build} 11 | 12 | image: Visual Studio 2022 13 | 14 | environment: 15 | SIGNPATH_API_TOKEN: 16 | secure: X+3/AmaBUkFqhOrSEGhY3MWjDmJsxvXlf6Nbfcw1OJurUlGF8w8gtl3sJkDkOnjj 17 | 18 | #---------------------------------# 19 | # build configuration # 20 | #---------------------------------# 21 | 22 | before_build: 23 | - pwsh: Install-PSResource -Name SignPath -TrustRepository 24 | 25 | # to run your custom scripts instead of automatic MSBuild 26 | build_script: build.bat sign 27 | 28 | #---------------------------------# 29 | # tests configuration # 30 | #---------------------------------# 31 | 32 | test: off 33 | 34 | #---------------------------------# 35 | # artifacts configuration # 36 | #---------------------------------# 37 | 38 | artifacts: 39 | - path: 'build\**\*.nupkg' 40 | - path: 'build\**\*.zip' 41 | 42 | #---------------------------------# 43 | # deployment configuration # 44 | #---------------------------------# 45 | 46 | deploy: 47 | - provider: NuGet 48 | api_key: 49 | secure: TbTVJ4/NFEEbpEKs2TD+co6u34RER9x3uG66ZgRi0xcAPekVJz26GCIVan5BTlvm 50 | on: 51 | appveyor_repo_tag: true 52 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | .nuget\NuGet.exe restore .nuget\packages.config -OutputDirectory packages -UseLockFile -LockedMode -NoHttpCache || exit /b 666 3 | 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 }; }" 4 | exit /B %errorlevel% 5 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HangfireIO/Hangfire.Azure.ServiceBusQueue/a28ce318fc984642fc5d764d90075abedafb448d/icon.png -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Hangfire OÜ, Adam Barclay, Giampaolo Gabba 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /nuspecs/Hangfire.Azure.ServiceBusQueue.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Hangfire.Azure.ServiceBusQueue 5 | %version% 6 | Hangfire Azure ServiceBus Queue 7 | Sergey Odinokov, Adam Barclay, Giampaolo Gabba 8 | HangfireIO, odinserj 9 | https://github.com/HangfireIO/HangFire.Azure.ServiceBusQueue 10 | 11 | MIT 12 | icon.png 13 | Azure ServiceBus Queue support for SQL Server job storage implementation 14 | Copyright © 2015 Hangfire OÜ, Adam Barclay, Giampaolo Gabba 15 | readme.md 16 | Hangfire Azure ServiceBus SqlServer 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /psake-project.ps1: -------------------------------------------------------------------------------- 1 | Include "packages\Hangfire.Build.0.5.0\tools\psake-common.ps1" 2 | 3 | Task Default -Depends Collect 4 | 5 | Task Collect -Depends Compile -Description "Copy all artifacts to the build folder." { 6 | Collect-Assembly "Hangfire.Azure.ServiceBusQueue" "net461" 7 | Collect-Assembly "Hangfire.Azure.ServiceBusQueue" "netstandard2.0" 8 | Collect-File "license.txt" 9 | Collect-File "readme.md" 10 | Collect-File "icon.png" 11 | } 12 | 13 | Task Pack -Depends Collect -Description "Create NuGet packages and archive files." { 14 | $version = Get-PackageVersion 15 | 16 | Create-Package "Hangfire.Azure.ServiceBusQueue" $version 17 | Create-Archive "Hangfire.Azure.ServiceBusQueue-$version" 18 | } 19 | 20 | Task Sign -Depends Pack -Description "Sign artifacts." { 21 | $version = Get-PackageVersion 22 | 23 | Sign-ArchiveContents "Hangfire.Azure.ServiceBusQueue-$version" "hangfire" 24 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Hangfire.Azure.ServiceBusQueue 2 | ============================ 3 | 4 | [![Official Site](https://img.shields.io/badge/site-hangfire.io-blue.svg)](http://hangfire.io) [![Latest version](https://img.shields.io/badge/nuget-latest-blue.svg)](https://www.nuget.org/packages/HangFire.Azure.ServiceBusQueue/) [![Build status](https://ci.appveyor.com/api/projects/status/3l7dued0cvkjascj?svg=true)](https://ci.appveyor.com/project/odinserj/hangfire-azure-servicebusqueue) [![License MIT](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT) 5 | 6 | What is it? 7 | ----------- 8 | 9 | Adds support for using Azure Service Bus Queues with [Hangfire](http://hangfire.io)'s SQL storage provider to reduce latency and remove the need to poll the database for new jobs. 10 | 11 | All job data continues to be stored and maintained within SQL storage, but polling is removed in favour of pushing the job ids through the service bus. 12 | 13 | Installation 14 | ------------- 15 | 16 | Hangfire.Azure.ServiceBusQueue is available as a NuGet package. Install it using the NuGet Package Console window: 17 | 18 | ``` 19 | PM> Install-Package Hangfire.Azure.ServiceBusQueue 20 | ``` 21 | 22 | Compatibility 23 | ------------- 24 | 25 | Hangfire v1.7+ introduced breaking changes to the SQL Server integration points and requires at least version 4.0.0 of this library. If you are on an older version of Hangfire please use a lower version of Hangfire.Azure.ServiceBusQueue 26 | 27 | Usage 28 | ------ 29 | 30 | To use the queue it needs to be added to your existing SQL Server storage configuration. 31 | 32 | 33 | For .NETCore and beyond, you can use the `IGlobalConfiguration` extension `.UseServiceBusQueues`: 34 | 35 | ```csharp 36 | 37 | //You can use .UseServiceBusQueues only after .UseSqlStorage() 38 | 39 | // Uses default options (no prefix or configuration) with the "default" queue only 40 | services.AddHangfire(configuration => configuration 41 | .UseSqlServerStorage("") 42 | .UseServiceBusQueues("") 43 | 44 | // Uses default options (no prefix or configuration) with the "critical" and "default" queues 45 | services.AddHangfire(configuration => configuration 46 | .UseSqlServerStorage("") 47 | .UseServiceBusQueues("", "critical", "default") 48 | 49 | // Configures queues on creation and uses the "crtical" and "default" queues 50 | services.AddHangfire(configuration => configuration 51 | .UseSqlServerStorage("") 52 | .UseServiceBusQueues("", 53 | queueOptions => { 54 | queueOptions.MaxSizeInMegabytes = 5120; 55 | queueOptions.DefaultMessageTimeToLive = new TimeSpan(0, 1, 0); 56 | } "critical", "default") 57 | 58 | // Specifies all options 59 | services.AddHangfire(configuration => configuration 60 | .UseSqlServerStorage("") 61 | .UseServiceBusQueues(new ServiceBusQueueOptions 62 | { 63 | ConnectionString = connectionString, 64 | 65 | Configure = configureAction, 66 | 67 | // The actual queues used in Azure will have this prefix if specified 68 | // (e.g. the "default" queue will be created as "my-prefix-default") 69 | // 70 | // This can be useful in development environments particularly where the machine 71 | // name could be used to separate individual developers machines automatically 72 | // (i.e. "my-prefix-{machine-name}".Replace("{machine-name}", Environment.MachineName)) 73 | QueuePrefix = "my-prefix-", 74 | 75 | // The queues to monitor. This *must* be specified, even to set just 76 | // the default queue as done here 77 | Queues = new [] { EnqueuedState.DefaultQueue }, 78 | 79 | // By default queues will be checked and created on startup. This option 80 | // can be disabled if the application will only be sending / listening to 81 | // the queue and you want to remove the 'Manage' permission from the shared 82 | // access policy. 83 | // 84 | // Note that the dashboard *must* have the 'Manage' permission otherwise the 85 | // queue length cannot be read 86 | CheckAndCreateQueues = false, 87 | 88 | // Typically a lower value is desired to keep the throughput of message processing high. A lower timeout means more calls to 89 | // Azure Service Bus which can increase costs, especially on an under-utilised server with few jobs. 90 | // Use a Higher value for lower costs in non production or non critical jobs 91 | LoopReceiveTimeout = TimeSpan.FromMilliseconds(500) 92 | 93 | // Delay between queue polling requests 94 | QueuePollInterval = TimeSpan.Zero 95 | })); 96 | ``` 97 | You can also use `UseServiceBusQueues` overloads: 98 | 99 | ```csharp 100 | var sqlStorage = new SqlServerStorage(""); 101 | 102 | // The connection string *must* be for the root namespace and have the "Manage" 103 | // permission if used by the dashboard 104 | var connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString"); 105 | 106 | // You can configure queues on first creation using this action 107 | Action configureAction = qd => 108 | { 109 | qd.MaxSizeInMegabytes = 5120; 110 | qd.DefaultMessageTimeToLive = new TimeSpan(0, 1, 0); 111 | }; 112 | 113 | // Uses default options (no prefix or configuration) with the "default" queue only 114 | sqlStorage.UseServiceBusQueues(connectionString); 115 | 116 | // Uses default options (no prefix or configuration) with the "critical" and "default" queues 117 | sqlStorage.UseServiceBusQueues(connectionString, "critical", "default"); 118 | 119 | // Configures queues on creation and uses the "crtical" and "default" queues 120 | sqlStorage.UseServiceBusQueues(connectionString, configureAction, "critical", "default"); 121 | 122 | // Specifies all options 123 | sqlStorage.UseServiceBusQueues(new ServiceBusQueueOptions 124 | { 125 | ConnectionString = connectionString, 126 | 127 | Credential = new DefaultAzureCredential() // any Azure.Identity.TokenCredential 128 | 129 | Configure = configureAction, 130 | 131 | // The actual queues used in Azure will have this prefix if specified 132 | // (e.g. the "default" queue will be created as "my-prefix-default") 133 | // 134 | // This can be useful in development environments particularly where the machine 135 | // name could be used to separate individual developers machines automatically 136 | // (i.e. "my-prefix-{machine-name}".Replace("{machine-name}", Environment.MachineName)) 137 | QueuePrefix = "my-prefix-", 138 | 139 | // The queues to monitor. This *must* be specified, even to set just 140 | // the default queue as done here 141 | Queues = new [] { EnqueuedState.DefaultQueue }, 142 | 143 | // By default queues will be checked and created on startup. This option 144 | // can be disabled if the application will only be sending / listening to 145 | // the queue and you want to remove the 'Manage' permission from the shared 146 | // access policy. 147 | // 148 | // Note that the dashboard *must* have the 'Manage' permission otherwise the 149 | // queue length cannot be read 150 | CheckAndCreateQueues = false, 151 | 152 | // Typically a lower value is desired to keep the throughput of message processing high. A lower timeout means more calls to 153 | // Azure Service Bus which can increase costs, especially on an under-utilised server with few jobs. 154 | // Use a Higher value for lower costs in non production or non critical jobs 155 | LoopReceiveTimeout = TimeSpan.FromMilliseconds(500) 156 | 157 | // Delay between queue polling requests 158 | QueuePollInterval = TimeSpan.Zero 159 | }); 160 | 161 | GlobalConfiguration.Configuration 162 | .UseStorage(sqlStorage); 163 | ``` 164 | 165 | Questions? Problems? 166 | --------------------- 167 | 168 | Open-source project are developing more smoothly, when all discussions are held in public. 169 | 170 | If you have any questions or problems related to Hangfire itself or this queue implementation or want to discuss new features, please visit the [discussion forum](http://discuss.hangfire.io). You can sign in there using your existing Google or GitHub account, so it's very simple to start using it. 171 | 172 | 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 behavours are welcome. 173 | 174 | License 175 | -------- 176 | 177 | Hangfire.Azure.ServiceBusQueues is released under the [MIT License](http://www.opensource.org/licenses/MIT). 178 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | true 7 | embedded 8 | true 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | true 26 | latest 27 | Default 28 | true 29 | 30 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/AsyncHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Hangfire.Azure.ServiceBusQueue 6 | { 7 | //https://github.com/aspnet/AspNetIdentity/blob/main/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs 8 | //Without the culture thing wich we dont need 9 | internal static class AsyncHelper 10 | { 11 | private static readonly TaskFactory MyTaskFactory = new 12 | TaskFactory(CancellationToken.None, 13 | TaskCreationOptions.None, 14 | TaskContinuationOptions.None, 15 | TaskScheduler.Default); 16 | 17 | public static TResult RunSync(Func> func) 18 | { 19 | return MyTaskFactory 20 | .StartNew(func) 21 | .Unwrap() 22 | .GetAwaiter() 23 | .GetResult(); 24 | } 25 | 26 | public static void RunSync(Func func) 27 | { 28 | MyTaskFactory 29 | .StartNew(func) 30 | .Unwrap() 31 | .GetAwaiter() 32 | .GetResult(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/HangFire.Azure.ServiceBusQueue.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net461;netstandard2.0 4 | CS1574;CS1591 5 | Hangfire.Azure.ServiceBusQueue 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/IRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Hangfire.Azure.ServiceBusQueue 5 | { 6 | public interface IRetryPolicy 7 | { 8 | Task Execute(Func action); 9 | } 10 | } -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/LinearRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Hangfire.Azure.ServiceBusQueue 7 | { 8 | public class LinearRetryPolicy : IRetryPolicy 9 | { 10 | private readonly List _retryDelays = new List(); 11 | 12 | public LinearRetryPolicy(int retryCount, TimeSpan retryDelay) 13 | { 14 | if (retryCount <= 0) 15 | throw new ArgumentOutOfRangeException(nameof(retryCount)); 16 | 17 | if (retryDelay <= TimeSpan.Zero) 18 | throw new ArgumentOutOfRangeException(nameof(retryCount)); 19 | 20 | for (var i = 0; i < retryCount; i++) 21 | { 22 | _retryDelays.Add(retryDelay); 23 | } 24 | } 25 | 26 | public LinearRetryPolicy(IEnumerable retryDelay) 27 | { 28 | if (retryDelay == null) 29 | throw new ArgumentNullException(nameof(retryDelay)); 30 | 31 | var timeSpans = retryDelay.ToList(); 32 | if (!timeSpans.Any() || timeSpans.Any(x=>x <= TimeSpan.Zero)) 33 | throw new ArgumentOutOfRangeException(nameof(retryDelay)); 34 | 35 | _retryDelays = timeSpans.ToList(); 36 | } 37 | 38 | public async Task Execute(Func action) 39 | { 40 | var exceptions = new List(); 41 | 42 | var attempt = 0; 43 | foreach (var retryDelay in _retryDelays) 44 | { 45 | attempt++; 46 | try 47 | { 48 | if (attempt > 1) 49 | await Task.Delay(retryDelay).ConfigureAwait(false); 50 | 51 | await action().ConfigureAwait(false); 52 | 53 | return; 54 | } 55 | catch (Exception ex) 56 | { 57 | exceptions.Add(ex); 58 | } 59 | } 60 | throw new AggregateException(exceptions); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: Guid("90fafb33-186a-47f9-84d8-fd516496a697")] 5 | [assembly: InternalsVisibleTo("HangFire.Azure.ServiceBusQueue.Tests")] 6 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/QueueDescription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure.Messaging.ServiceBus.Administration; 3 | 4 | namespace Hangfire.Azure.ServiceBusQueue 5 | { 6 | /// 7 | /// 8 | /// 9 | public class QueueDescription : CreateQueueOptions 10 | { 11 | public QueueDescription(string name) : base(name) 12 | { 13 | } 14 | 15 | public QueueDescription(QueueProperties queue) : base(queue) 16 | { 17 | } 18 | 19 | public string Path 20 | { 21 | get => Name; 22 | set => Name = value; 23 | } 24 | 25 | public bool EnableDeadLetteringOnMessageExpiration 26 | { 27 | get => DeadLetteringOnMessageExpiration; 28 | set => DeadLetteringOnMessageExpiration = value; 29 | } 30 | 31 | [Obsolete("This property is no longer in use in the latest azure SDK and has no effect on the queue creation")] 32 | public bool EnableExpress { get; set; } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusGlobalConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Hangfire.Annotations; 3 | using Hangfire.SqlServer; 4 | using Hangfire.States; 5 | 6 | namespace Hangfire.Azure.ServiceBusQueue 7 | { 8 | public static class ServiceBusGlobalConfigurationExtensions 9 | { 10 | public static IGlobalConfiguration UseServiceBusQueues( 11 | [NotNull] this IGlobalConfiguration configuration, 12 | [NotNull] string connectionString) 13 | { 14 | return UseServiceBusQueues(configuration, new ServiceBusQueueOptions 15 | { 16 | ConnectionString = connectionString, 17 | Queues = new[] { EnqueuedState.DefaultQueue } 18 | }); 19 | } 20 | 21 | public static IGlobalConfiguration UseServiceBusQueues( 22 | [NotNull] this IGlobalConfiguration configuration, 23 | [NotNull] string connectionString, 24 | params string[] queues) 25 | { 26 | return UseServiceBusQueues(configuration, new ServiceBusQueueOptions 27 | { 28 | ConnectionString = connectionString, 29 | Queues = queues 30 | }); 31 | } 32 | 33 | public static IGlobalConfiguration UseServiceBusQueues( 34 | [NotNull] this IGlobalConfiguration configuration, 35 | [NotNull] string connectionString, 36 | Action configureAction, 37 | params string[] queues) 38 | { 39 | return UseServiceBusQueues(configuration, new ServiceBusQueueOptions 40 | { 41 | ConnectionString = connectionString, 42 | Configure = configureAction, 43 | Queues = queues 44 | }); 45 | } 46 | 47 | public static IGlobalConfiguration UseServiceBusQueues( 48 | [NotNull] this IGlobalConfiguration configuration, 49 | [NotNull] ServiceBusQueueOptions options) 50 | { 51 | var sqlServerStorage = configuration.Entry; 52 | var provider = new ServiceBusQueueJobQueueProvider(options); 53 | sqlServerStorage.QueueProviders.Add(provider, options.Queues); 54 | return configuration; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Hangfire.Logging; 4 | using System.Threading.Tasks; 5 | using Azure; 6 | using Azure.Messaging.ServiceBus; 7 | using Azure.Messaging.ServiceBus.Administration; 8 | 9 | namespace Hangfire.Azure.ServiceBusQueue 10 | { 11 | internal class ServiceBusManager 12 | { 13 | private readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 14 | 15 | // Stores the pre-created QueueClients (note the key is the unprefixed queue name) 16 | private readonly Dictionary _senders; 17 | private readonly Dictionary _receivers; 18 | 19 | private readonly ServiceBusAdministrationClient _managementAdminClient; 20 | private readonly ServiceBusClient _managementClient; 21 | 22 | public ServiceBusManager(ServiceBusQueueOptions options) 23 | { 24 | Options = options ?? throw new ArgumentNullException(nameof(options)); 25 | 26 | _senders = new Dictionary(options.Queues.Length); 27 | _receivers = new Dictionary(options.Queues.Length); 28 | _managementClient = options.Credential != null ? new ServiceBusClient(options.ConnectionString, options.Credential) : new ServiceBusClient(options.ConnectionString); 29 | _managementAdminClient = options.Credential != null ? new ServiceBusAdministrationClient(options.ConnectionString, options.Credential) : new ServiceBusAdministrationClient(options.ConnectionString); 30 | 31 | if (options.CheckAndCreateQueues) 32 | { 33 | AsyncHelper.RunSync(CreateQueueClients); 34 | } 35 | } 36 | 37 | ~ServiceBusManager() 38 | { 39 | foreach (var sender in _senders) 40 | { 41 | AsyncHelper.RunSync(() => sender.Value.CloseAsync()); 42 | } 43 | foreach (var receiver in _receivers) 44 | { 45 | AsyncHelper.RunSync(() => receiver.Value.CloseAsync()); 46 | } 47 | _managementClient.DisposeAsync(); 48 | } 49 | 50 | public ServiceBusQueueOptions Options { get; } 51 | 52 | public async Task GetSenderAsync(string queue) 53 | { 54 | if (_senders.Count != Options.Queues.Length) 55 | { 56 | await CreateQueueClients().ConfigureAwait(false); 57 | } 58 | return _senders[queue]; 59 | } 60 | 61 | public async Task GetReceiverAsync(string queue) 62 | { 63 | if (_receivers.Count != Options.Queues.Length) 64 | { 65 | await CreateQueueClients().ConfigureAwait(false); 66 | } 67 | 68 | return _receivers[queue]; 69 | } 70 | 71 | public async Task> GetQueueRuntimeInfoAsync(string queue) 72 | { 73 | return await _managementAdminClient.GetQueueRuntimePropertiesAsync(Options.GetQueueName(queue)); 74 | } 75 | 76 | private async Task CreateQueueClients() 77 | { 78 | foreach (var queue in Options.Queues) 79 | { 80 | var prefixedQueue = Options.GetQueueName(queue); 81 | 82 | await CreateQueueIfNotExistsAsync(prefixedQueue).ConfigureAwait(false); 83 | 84 | _logger.TraceFormat("Creating new Senders and Receivers for queue {0}", prefixedQueue); 85 | 86 | if (!_senders.ContainsKey(queue)) 87 | _senders.Add(queue, _managementClient.CreateSender(prefixedQueue)); 88 | 89 | if (!_receivers.ContainsKey(queue)) 90 | _receivers.Add(queue, _managementClient.CreateReceiver(prefixedQueue)); 91 | } 92 | } 93 | 94 | private async Task CreateQueueIfNotExistsAsync(string prefixedQueue) 95 | { 96 | if (Options.CheckAndCreateQueues == false) 97 | { 98 | _logger.InfoFormat("Not checking for the existence of the queue {0}", prefixedQueue); 99 | return; 100 | } 101 | try 102 | { 103 | _logger.InfoFormat("Checking if queue {0} exists", prefixedQueue); 104 | 105 | if (await _managementAdminClient.QueueExistsAsync(prefixedQueue).ConfigureAwait(false)) 106 | { 107 | return; 108 | } 109 | 110 | _logger.InfoFormat("Creating new queue {0}", prefixedQueue); 111 | 112 | var queueOptions = new QueueDescription(prefixedQueue); 113 | if (Options.RequiresDuplicateDetection != null) 114 | { 115 | queueOptions.RequiresDuplicateDetection = Options.RequiresDuplicateDetection.Value; 116 | } 117 | 118 | Options.Configure?.Invoke(queueOptions); 119 | 120 | await _managementAdminClient.CreateQueueAsync(queueOptions).ConfigureAwait(false); 121 | } 122 | catch (UnauthorizedAccessException ex) 123 | { 124 | var errorMessage = 125 | $"Queue '{prefixedQueue}' could not be checked / created, likely due to missing the 'Manage' permission. " + 126 | "You must either grant the 'Manage' permission, or set ServiceBusQueueOptions.CheckAndCreateQueues to false"; 127 | 128 | throw new UnauthorizedAccessException(errorMessage, ex); 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusQueueFetchedJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Messaging.ServiceBus; 5 | using Hangfire.Logging; 6 | using Hangfire.Storage; 7 | 8 | namespace Hangfire.Azure.ServiceBusQueue 9 | { 10 | internal class ServiceBusQueueFetchedJob : IFetchedJob 11 | { 12 | private readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 13 | private readonly ServiceBusReceiver _client; 14 | private readonly TimeSpan? _lockRenewalDelay; 15 | private readonly CancellationTokenSource _cancellationTokenSource; 16 | private readonly ServiceBusReceivedMessage _message; 17 | 18 | private bool _completed; 19 | private bool _disposed; 20 | 21 | public ServiceBusQueueFetchedJob(ServiceBusReceiver client, ServiceBusReceivedMessage message, TimeSpan? lockRenewalDelay) 22 | { 23 | _message = message ?? throw new ArgumentNullException(nameof(message)); 24 | _client = client; 25 | _lockRenewalDelay = lockRenewalDelay; 26 | _cancellationTokenSource = new CancellationTokenSource(); 27 | 28 | JobId = message.Body.ToString(); 29 | 30 | KeepAlive(); 31 | } 32 | 33 | public string JobId { get; } 34 | public ServiceBusReceivedMessage Message => _message; 35 | 36 | public void Requeue() 37 | { 38 | _cancellationTokenSource.Cancel(); 39 | 40 | try 41 | { 42 | AsyncHelper.RunSync(() => _client.AbandonMessageAsync(_message)); 43 | _completed = true; 44 | } 45 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessageNotFound) 46 | { 47 | _logger.Warn($"Message with token '{_message.LockToken}' not found in service bus."); 48 | } 49 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessageLockLost) 50 | { 51 | } 52 | } 53 | 54 | public void RemoveFromQueue() 55 | { 56 | _cancellationTokenSource.Cancel(); 57 | 58 | try 59 | { 60 | AsyncHelper.RunSync(() => _client.CompleteMessageAsync(_message)); 61 | _completed = true; 62 | } 63 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessageNotFound) 64 | { 65 | _logger.Warn($"Message with token '{_message.LockToken}' not found in service bus."); 66 | } 67 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessageLockLost) 68 | { 69 | } 70 | } 71 | 72 | public void Dispose() 73 | { 74 | _cancellationTokenSource.Cancel(); 75 | _cancellationTokenSource.Dispose(); 76 | 77 | try 78 | { 79 | if (!_completed && !_disposed) 80 | { 81 | AsyncHelper.RunSync(() => _client.AbandonMessageAsync(_message)); 82 | } 83 | } 84 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessageNotFound 85 | || ex.Reason == ServiceBusFailureReason.MessageLockLost) 86 | { 87 | } 88 | 89 | _disposed = true; 90 | } 91 | 92 | private void KeepAlive() 93 | { 94 | Task.Run(async () => 95 | { 96 | while (!_cancellationTokenSource.Token.IsCancellationRequested) 97 | { 98 | // Previously we were waiting until a second before the lock is due 99 | // to expire to give us plenty of time to wake up and communicate 100 | // with queue to renew the lock of this message before expiration. 101 | // However since clocks may be non-synchronized well, for long-running 102 | // background jobs it's better to have more renewal attempts than a 103 | // lock that's expired too early. 104 | var toWait = _lockRenewalDelay ?? 105 | _message.LockedUntil - DateTime.UtcNow - TimeSpan.FromSeconds(1); 106 | 107 | await Task.Delay(toWait, _cancellationTokenSource.Token).ConfigureAwait(false); 108 | 109 | // Double check we have not been cancelled to avoid renewing a lock 110 | // unnecessarily 111 | if (_cancellationTokenSource.Token.IsCancellationRequested) continue; 112 | 113 | try 114 | { 115 | await _client.RenewMessageLockAsync(_message).ConfigureAwait(false); 116 | } 117 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessageNotFound 118 | || ex.Reason == ServiceBusFailureReason.MessageLockLost) 119 | { 120 | break; 121 | } 122 | catch (Exception ex) 123 | { 124 | _logger.DebugException( 125 | $"An exception was thrown while trying to renew a lock for job '{JobId}'.", ex); 126 | } 127 | } 128 | }, _cancellationTokenSource.Token); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusQueueJobQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Transactions; 5 | using Hangfire.SqlServer; 6 | using Hangfire.Storage; 7 | using System.Threading.Tasks; 8 | using System.Data.Common; 9 | using Azure.Messaging.ServiceBus; 10 | using Hangfire.Logging; 11 | 12 | namespace Hangfire.Azure.ServiceBusQueue 13 | { 14 | internal class ServiceBusQueueJobQueue : IPersistentJobQueue 15 | { 16 | private readonly ServiceBusManager _manager; 17 | private readonly ServiceBusQueueOptions _options; 18 | private readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 19 | 20 | public ServiceBusQueueJobQueue(ServiceBusManager manager, ServiceBusQueueOptions options) 21 | { 22 | _manager = manager ?? throw new ArgumentNullException(nameof(manager)); 23 | _options = options ?? throw new ArgumentNullException(nameof(options)); 24 | } 25 | 26 | public IFetchedJob Dequeue(string[] queues, CancellationToken cancellationToken) 27 | { 28 | if (queues == null) 29 | throw new ArgumentNullException(nameof(queues)); 30 | 31 | if (queues.Length == 0) 32 | throw new ArgumentException("Queue array must not be empty.", nameof(queues)); 33 | 34 | return Task.Run(async () => 35 | { 36 | var queueIndex = 0; 37 | var receivers = await Task.WhenAll(queues.Select(queue => _manager.GetReceiverAsync(queue))).ConfigureAwait(false); 38 | 39 | do 40 | { 41 | cancellationToken.ThrowIfCancellationRequested(); 42 | var busReceiver = receivers[queueIndex]; 43 | 44 | try 45 | { 46 | var message = await busReceiver.ReceiveMessageAsync(_manager.Options.LoopReceiveTimeout, cancellationToken) 47 | .ConfigureAwait(false); 48 | 49 | if (message != null) 50 | { 51 | _logger.Info( 52 | $"{DateTime.Now} - Dequeue one message from queue {busReceiver.EntityPath} with body {message.Body}"); 53 | return new ServiceBusQueueFetchedJob(busReceiver, message, _options.LockRenewalDelay); 54 | } 55 | } 56 | catch (TaskCanceledException) 57 | { 58 | _logger.Warn($"a cancellation has been requested for the queue {busReceiver.EntityPath}"); 59 | } 60 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.ServiceTimeout) 61 | { 62 | _logger.Warn("Servicebus timeout dequeuing one message from queue {busReceiver.EntityPath}"); 63 | } 64 | catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessagingEntityNotFound) 65 | { 66 | var errorMessage = 67 | $"Queue {busReceiver.EntityPath} could not be found. Either create the queue manually, " + 68 | "or grant the Manage permission and set ServiceBusQueueOptions.CheckAndCreateQueues to true"; 69 | 70 | throw new UnauthorizedAccessException(errorMessage, ex); 71 | } 72 | 73 | queueIndex = (queueIndex + 1) % queues.Length; 74 | 75 | await Task.Delay(_options.QueuePollInterval).ConfigureAwait(false); 76 | 77 | } while (true); 78 | }).GetAwaiter().GetResult(); 79 | } 80 | 81 | #if NETSTANDARD2_0 82 | public void Enqueue(DbConnection connection, DbTransaction transaction, string queue, string jobId) 83 | #else 84 | public void Enqueue(System.Data.IDbConnection connection, string queue, string jobId) 85 | #endif 86 | { 87 | // Because we are within a TransactionScope at this point the below 88 | // call would not work (Local transactions are not supported with other resource managers/DTC 89 | // exception is thrown) without suppression 90 | using (new TransactionScope(TransactionScopeOption.Suppress)) 91 | { 92 | AsyncHelper.RunSync(() => DoEnqueueAsync(queue, jobId)); 93 | } 94 | } 95 | 96 | private async Task DoEnqueueAsync(string queue, string jobId) 97 | { 98 | var sender = await _manager.GetSenderAsync(queue).ConfigureAwait(false); 99 | 100 | var message = new ServiceBusMessage(jobId) { MessageId = jobId }; 101 | await _manager.Options.RetryPolicy.Execute(() => sender.SendMessageAsync(message)).ConfigureAwait(false); 102 | _logger.Info($"{DateTime.Now} - Enqueue one message to queue {sender.EntityPath} with body {jobId}"); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusQueueJobQueueProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Hangfire.SqlServer; 3 | 4 | namespace Hangfire.Azure.ServiceBusQueue 5 | { 6 | using Hangfire.Logging; 7 | 8 | internal class ServiceBusQueueJobQueueProvider : IPersistentJobQueueProvider 9 | { 10 | private static readonly ILog Logger = LogProvider.GetCurrentClassLogger(); 11 | 12 | private readonly ServiceBusQueueJobQueue _jobQueue; 13 | private readonly ServiceBusQueueMonitoringApi _monitoringApi; 14 | 15 | public ServiceBusQueueJobQueueProvider(ServiceBusQueueOptions options) 16 | { 17 | if (options == null) throw new ArgumentNullException(nameof(options)); 18 | 19 | options.Validate(); 20 | 21 | Logger.Info("Using the following options for Azure service bus:"); 22 | Logger.InfoFormat(" Check and create queues: {0}", options.CheckAndCreateQueues); 23 | Logger.InfoFormat(" Queue prefix: {0}", options.QueuePrefix); 24 | Logger.InfoFormat(" Queues: [{0}]", string.Join(", ", options.Queues)); 25 | 26 | var manager = new ServiceBusManager(options); 27 | 28 | _jobQueue = new ServiceBusQueueJobQueue(manager, options); 29 | _monitoringApi = new ServiceBusQueueMonitoringApi(manager, options.Queues); 30 | } 31 | 32 | public IPersistentJobQueue GetJobQueue() 33 | { 34 | return _jobQueue; 35 | } 36 | 37 | public IPersistentJobQueueMonitoringApi GetJobQueueMonitoringApi() 38 | { 39 | return _monitoringApi; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusQueueMonitoringApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Hangfire.SqlServer; 6 | 7 | namespace Hangfire.Azure.ServiceBusQueue 8 | { 9 | internal class ServiceBusQueueMonitoringApi : IPersistentJobQueueMonitoringApi 10 | { 11 | private readonly ServiceBusManager _manager; 12 | private readonly string[] _queues; 13 | 14 | public ServiceBusQueueMonitoringApi(ServiceBusManager manager, string[] queues) 15 | { 16 | _manager = manager ?? throw new ArgumentNullException(nameof(manager)); 17 | _queues = queues ?? throw new ArgumentNullException(nameof(queues)); 18 | } 19 | 20 | public IEnumerable GetQueues() 21 | { 22 | return _queues; 23 | } 24 | 25 | public IEnumerable GetEnqueuedJobIds(string queue, int from, int perPage) 26 | { 27 | return AsyncHelper.RunSync(() => GetEnqueuedJobIdsAsync(queue, from, perPage)); 28 | } 29 | 30 | private async Task> GetEnqueuedJobIdsAsync(string queue, int from, int perPage) 31 | { 32 | var receiver = await _manager.GetReceiverAsync(queue).ConfigureAwait(false); 33 | 34 | var jobIds = new List(); 35 | 36 | // Hangfire api require a 0 based index for @from, but PeekMessageAsync is 1 based 37 | var messages = await receiver.PeekMessagesAsync(perPage, from + 1).ConfigureAwait(false); 38 | 39 | foreach (var msg in messages) 40 | { 41 | if (long.TryParse(msg.Body.ToString(), out var longJobId)) 42 | { 43 | jobIds.Add(longJobId); 44 | } 45 | } 46 | 47 | return jobIds; 48 | } 49 | 50 | public IEnumerable GetFetchedJobIds(string queue, int from, int perPage) 51 | { 52 | return Enumerable.Empty(); 53 | } 54 | 55 | public EnqueuedAndFetchedCountDto GetEnqueuedAndFetchedCount(string queue) 56 | { 57 | return AsyncHelper.RunSync(() => GetEnqueuedAndFetchedCountAsync(queue)); 58 | } 59 | 60 | private async Task GetEnqueuedAndFetchedCountAsync(string queue) 61 | { 62 | var queueRuntimeInfo = await _manager.GetQueueRuntimeInfoAsync(queue).ConfigureAwait(false); 63 | 64 | return new EnqueuedAndFetchedCountDto 65 | { 66 | EnqueuedCount = (int)queueRuntimeInfo.Value.ActiveMessageCount, 67 | FetchedCount = null 68 | }; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusQueueOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure.Core; 3 | 4 | namespace Hangfire.Azure.ServiceBusQueue 5 | { 6 | public class ServiceBusQueueOptions 7 | { 8 | public ServiceBusQueueOptions() 9 | { 10 | QueuePollInterval = TimeSpan.Zero; 11 | CheckAndCreateQueues = true; 12 | LoopReceiveTimeout = TimeSpan.FromMilliseconds(500); 13 | RetryPolicy = new LinearRetryPolicy(3, TimeSpan.FromSeconds(1)); 14 | } 15 | 16 | private TimeSpan _queuePollInterval; 17 | 18 | public TimeSpan QueuePollInterval 19 | { 20 | get => _queuePollInterval; 21 | set 22 | { 23 | var message = $"The QueuePollInterval property value should be positive. Given: {value}."; 24 | _queuePollInterval = !(value != value.Duration()) ? value : throw new ArgumentException(message, nameof(value)); 25 | } 26 | } 27 | 28 | /// 29 | /// Gets or sets the prefix that will be prepended to all queue names in 30 | /// the service bus (e.g. if the prefix is "a-prefix-" then the default queue 31 | /// will be named "a-prefix-default" in Azure) 32 | /// 33 | public string QueuePrefix { get; set; } 34 | 35 | /// 36 | /// Configures a queue on construction, for example setting maximum message 37 | /// size or default TTL. 38 | /// 39 | public Action Configure { get; set; } 40 | 41 | /// 42 | /// Gets or sets a value which indicates whether or not to automatically create and 43 | /// configure queues. 44 | /// 45 | /// 46 | /// On initialisation if this property is true we will create and check all queues 47 | /// immediately, otherwise we delay the creation of the queue clients until they are first 48 | /// requested. 49 | /// 50 | public bool CheckAndCreateQueues { get; set; } 51 | 52 | /// 53 | /// Gets or sets a delay between calls to the method 54 | /// to disallow workers to pick up the same background job several time while it's still 55 | /// processing on an active server. 56 | /// 57 | public TimeSpan? LockRenewalDelay { get; set; } 58 | 59 | public string ConnectionString { get; set; } 60 | 61 | public TokenCredential Credential { get; set; } 62 | 63 | public string[] Queues { get; set; } 64 | 65 | /// 66 | /// Gets or sets a value which specifies the 67 | /// setting on creating a service bus queue. 68 | /// 69 | /// 70 | /// This can provide resilience against retried messages due to transient errors within Hangfire, but will not help 71 | /// with application-level issues. 72 | /// This setting can only be applied to premium tier namespace, leave null if using a standard tier namespace. 73 | /// 74 | public bool? RequiresDuplicateDetection { get; set; } 75 | 76 | /// 77 | /// Gets or sets a timeout that is used between loop runs of receiving messages from Azure Service Bus. This is the timeout 78 | /// used when waiting on the last queue before looping around again (does not apply when only a single-queue exists). 79 | /// 80 | /// 81 | /// Typically a lower value is desired to keep the throughput of message processing high. A lower timeout means more calls to 82 | /// Azure Service Bus which can increase costs, especially on an under-utilised server with few jobs. 83 | /// 84 | /// Defaults to TimeSpan.FromMilliseconds(500) 85 | public TimeSpan LoopReceiveTimeout { get; set; } 86 | 87 | /// 88 | /// Gets or sets the retry policy for enqueueing messages 89 | /// 90 | /// 91 | /// The default policy is a with of 3 92 | /// and a of 1 second. 93 | /// 94 | public IRetryPolicy RetryPolicy { get; set; } 95 | 96 | internal string GetQueueName(string name) 97 | { 98 | if (QueuePrefix != null) 99 | { 100 | return QueuePrefix + name; 101 | } 102 | 103 | return name; 104 | } 105 | 106 | internal void Validate() 107 | { 108 | if (ConnectionString == null) 109 | throw new InvalidOperationException("Must supply ConnectionString to ServiceBusQueueOptions"); 110 | 111 | if (Queues == null) 112 | throw new InvalidOperationException("Must supply Queues to ServiceBusQueueOptions"); 113 | 114 | if (Queues.Length == 0) 115 | throw new InvalidOperationException( 116 | "Must supply at least one queue in Queues property of ServiceBusQueueOptions"); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/ServiceBusQueueSqlServerStorageExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure.Messaging.ServiceBus.Administration; 3 | using Hangfire.SqlServer; 4 | using Hangfire.States; 5 | 6 | namespace Hangfire.Azure.ServiceBusQueue 7 | { 8 | public static class ServiceBusQueueSqlServerStorageExtensions 9 | { 10 | public static SqlServerStorage UseServiceBusQueues( 11 | this SqlServerStorage storage, 12 | string connectionString) 13 | { 14 | return UseServiceBusQueues(storage, new ServiceBusQueueOptions 15 | { 16 | ConnectionString = connectionString, 17 | Queues = new[] { EnqueuedState.DefaultQueue } 18 | }); 19 | } 20 | 21 | public static SqlServerStorage UseServiceBusQueues( 22 | this SqlServerStorage storage, 23 | string connectionString, 24 | params string[] queues) 25 | { 26 | return UseServiceBusQueues(storage, new ServiceBusQueueOptions 27 | { 28 | ConnectionString = connectionString, 29 | Queues = queues 30 | }); 31 | } 32 | 33 | public static SqlServerStorage UseServiceBusQueues( 34 | this SqlServerStorage storage, 35 | string connectionString, 36 | Action configureAction, 37 | params string[] queues) 38 | { 39 | return UseServiceBusQueues(storage, new ServiceBusQueueOptions 40 | { 41 | ConnectionString = connectionString, 42 | Configure = configureAction, 43 | Queues = queues 44 | }); 45 | } 46 | 47 | public static SqlServerStorage UseServiceBusQueues( 48 | this SqlServerStorage storage, 49 | ServiceBusQueueOptions options) 50 | { 51 | if (storage == null) throw new ArgumentNullException(nameof(storage)); 52 | if (options == null) throw new ArgumentNullException(nameof(options)); 53 | 54 | var provider = new ServiceBusQueueJobQueueProvider(options); 55 | 56 | storage.QueueProviders.Add(provider, options.Queues); 57 | 58 | return storage; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/HangFire.Azure.ServiceBusQueue/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETFramework,Version=v4.6.1": { 5 | "Azure.Messaging.ServiceBus": { 6 | "type": "Direct", 7 | "requested": "[7.19.0, )", 8 | "resolved": "7.19.0", 9 | "contentHash": "DoGfZpH6bwtsA6siIS5wv3SZZzOoNLnJi9N7OHhtBvE5Wlfn13jvwEe5z92HOddJVYEchdIZBK+jHBtPz1HFpg==", 10 | "dependencies": { 11 | "Azure.Core": "1.44.1", 12 | "Azure.Core.Amqp": "1.3.1", 13 | "Microsoft.Azure.Amqp": "2.6.9" 14 | } 15 | }, 16 | "Hangfire.Core": { 17 | "type": "Direct", 18 | "requested": "[1.7.0, )", 19 | "resolved": "1.7.0", 20 | "contentHash": "ZWfWcE+kOWjKy7l/rsVtZmxs5XdltJY5ndx+vy7enD85ZVgc0Z+nIOZ/PfBhQQ68vGOfwOudpQVHuwXlSn7/fQ==", 21 | "dependencies": { 22 | "Newtonsoft.Json": "5.0.1", 23 | "Owin": "1.0.0" 24 | } 25 | }, 26 | "Hangfire.SqlServer": { 27 | "type": "Direct", 28 | "requested": "[1.7.0, )", 29 | "resolved": "1.7.0", 30 | "contentHash": "2gWcsGu2ws16mAml2FPwabdfk/NI+Ap+xLBU2geCvi+1vFA0SiQVW67GB9Iv6JzaxCqq88JUsHMAzJPwYkJM4A==", 31 | "dependencies": { 32 | "Hangfire.Core": "[1.7.0]" 33 | } 34 | }, 35 | "Microsoft.CodeAnalysis.NetAnalyzers": { 36 | "type": "Direct", 37 | "requested": "[9.0.0, )", 38 | "resolved": "9.0.0", 39 | "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" 40 | }, 41 | "Microsoft.NETFramework.ReferenceAssemblies": { 42 | "type": "Direct", 43 | "requested": "[1.0.3, )", 44 | "resolved": "1.0.3", 45 | "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", 46 | "dependencies": { 47 | "Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3" 48 | } 49 | }, 50 | "Microsoft.SourceLink.GitHub": { 51 | "type": "Direct", 52 | "requested": "[8.0.0, )", 53 | "resolved": "8.0.0", 54 | "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", 55 | "dependencies": { 56 | "Microsoft.Build.Tasks.Git": "8.0.0", 57 | "Microsoft.SourceLink.Common": "8.0.0" 58 | } 59 | }, 60 | "Azure.Core": { 61 | "type": "Transitive", 62 | "resolved": "1.44.1", 63 | "contentHash": "YyznXLQZCregzHvioip07/BkzjuWNXogJEVz9T5W6TwjNr17ax41YGzYMptlo2G10oLCuVPoyva62y0SIRDixg==", 64 | "dependencies": { 65 | "Microsoft.Bcl.AsyncInterfaces": "6.0.0", 66 | "System.ClientModel": "1.1.0", 67 | "System.Diagnostics.DiagnosticSource": "6.0.1", 68 | "System.Memory.Data": "6.0.0", 69 | "System.Net.Http": "4.3.4", 70 | "System.Numerics.Vectors": "4.5.0", 71 | "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", 72 | "System.Text.Encodings.Web": "6.0.0", 73 | "System.Text.Json": "6.0.10", 74 | "System.Threading.Tasks.Extensions": "4.5.4" 75 | } 76 | }, 77 | "Azure.Core.Amqp": { 78 | "type": "Transitive", 79 | "resolved": "1.3.1", 80 | "contentHash": "AY1ZM4WwLBb9L2WwQoWs7wS2XKYg83tp3yVVdgySdebGN0FuIszuEqCy3Nhv6qHpbkjx/NGuOTsUbF/oNGBgwA==", 81 | "dependencies": { 82 | "Microsoft.Azure.Amqp": "2.6.7", 83 | "System.Memory": "4.5.4", 84 | "System.Memory.Data": "1.0.2" 85 | } 86 | }, 87 | "Microsoft.Azure.Amqp": { 88 | "type": "Transitive", 89 | "resolved": "2.6.9", 90 | "contentHash": "5i9XzfqxK1H5IBl+OuOV1jwJdrOvi5RUwsZgVOryZm0GCzcM9NWPNRxzPAbsSeaR2T6+1gGvdT3vR+Vbha6KFQ==" 91 | }, 92 | "Microsoft.Bcl.AsyncInterfaces": { 93 | "type": "Transitive", 94 | "resolved": "6.0.0", 95 | "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==", 96 | "dependencies": { 97 | "System.Threading.Tasks.Extensions": "4.5.4" 98 | } 99 | }, 100 | "Microsoft.Build.Tasks.Git": { 101 | "type": "Transitive", 102 | "resolved": "8.0.0", 103 | "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" 104 | }, 105 | "Microsoft.NETFramework.ReferenceAssemblies.net461": { 106 | "type": "Transitive", 107 | "resolved": "1.0.3", 108 | "contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA==" 109 | }, 110 | "Microsoft.SourceLink.Common": { 111 | "type": "Transitive", 112 | "resolved": "8.0.0", 113 | "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" 114 | }, 115 | "Newtonsoft.Json": { 116 | "type": "Transitive", 117 | "resolved": "5.0.1", 118 | "contentHash": "AuSDf0kpGGLSvFmj1Zia8BxTeUCdQ6lB8lWUZRYVXRnAQLmiEGmoP0M+9KHwJNqBW2FiFwSG8Jkz3G7tS6k7MQ==" 119 | }, 120 | "Owin": { 121 | "type": "Transitive", 122 | "resolved": "1.0.0", 123 | "contentHash": "OseTFniKmyp76mEzOBwIKGBRS5eMoYNkMKaMXOpxx9jv88+b6mh1rSaw43vjBOItNhaLFG3d0a20PfHyibH5sw==" 124 | }, 125 | "System.Buffers": { 126 | "type": "Transitive", 127 | "resolved": "4.5.1", 128 | "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" 129 | }, 130 | "System.ClientModel": { 131 | "type": "Transitive", 132 | "resolved": "1.1.0", 133 | "contentHash": "UocOlCkxLZrG2CKMAAImPcldJTxeesHnHGHwhJ0pNlZEvEXcWKuQvVOER2/NiOkJGRJk978SNdw3j6/7O9H1lg==", 134 | "dependencies": { 135 | "System.Memory.Data": "1.0.2", 136 | "System.Text.Json": "6.0.9" 137 | } 138 | }, 139 | "System.Diagnostics.DiagnosticSource": { 140 | "type": "Transitive", 141 | "resolved": "6.0.1", 142 | "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", 143 | "dependencies": { 144 | "System.Memory": "4.5.4", 145 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 146 | } 147 | }, 148 | "System.Memory": { 149 | "type": "Transitive", 150 | "resolved": "4.5.4", 151 | "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", 152 | "dependencies": { 153 | "System.Buffers": "4.5.1", 154 | "System.Numerics.Vectors": "4.5.0", 155 | "System.Runtime.CompilerServices.Unsafe": "4.5.3" 156 | } 157 | }, 158 | "System.Memory.Data": { 159 | "type": "Transitive", 160 | "resolved": "6.0.0", 161 | "contentHash": "ntFHArH3I4Lpjf5m4DCXQHJuGwWPNVJPaAvM95Jy/u+2Yzt2ryiyIN04LAogkjP9DeRcEOiviAjQotfmPq/FrQ==", 162 | "dependencies": { 163 | "System.Memory": "4.5.4", 164 | "System.Text.Json": "6.0.0" 165 | } 166 | }, 167 | "System.Net.Http": { 168 | "type": "Transitive", 169 | "resolved": "4.3.4", 170 | "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", 171 | "dependencies": { 172 | "System.Security.Cryptography.X509Certificates": "4.3.0" 173 | } 174 | }, 175 | "System.Numerics.Vectors": { 176 | "type": "Transitive", 177 | "resolved": "4.5.0", 178 | "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" 179 | }, 180 | "System.Runtime.CompilerServices.Unsafe": { 181 | "type": "Transitive", 182 | "resolved": "6.0.0", 183 | "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" 184 | }, 185 | "System.Runtime.InteropServices.RuntimeInformation": { 186 | "type": "Transitive", 187 | "resolved": "4.3.0", 188 | "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" 189 | }, 190 | "System.Security.Cryptography.Algorithms": { 191 | "type": "Transitive", 192 | "resolved": "4.3.0", 193 | "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", 194 | "dependencies": { 195 | "System.Security.Cryptography.Primitives": "4.3.0" 196 | } 197 | }, 198 | "System.Security.Cryptography.Encoding": { 199 | "type": "Transitive", 200 | "resolved": "4.3.0", 201 | "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" 202 | }, 203 | "System.Security.Cryptography.Primitives": { 204 | "type": "Transitive", 205 | "resolved": "4.3.0", 206 | "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" 207 | }, 208 | "System.Security.Cryptography.X509Certificates": { 209 | "type": "Transitive", 210 | "resolved": "4.3.0", 211 | "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", 212 | "dependencies": { 213 | "System.Security.Cryptography.Algorithms": "4.3.0", 214 | "System.Security.Cryptography.Encoding": "4.3.0" 215 | } 216 | }, 217 | "System.Text.Encodings.Web": { 218 | "type": "Transitive", 219 | "resolved": "6.0.0", 220 | "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", 221 | "dependencies": { 222 | "System.Buffers": "4.5.1", 223 | "System.Memory": "4.5.4", 224 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 225 | } 226 | }, 227 | "System.Text.Json": { 228 | "type": "Transitive", 229 | "resolved": "6.0.10", 230 | "contentHash": "NSB0kDipxn2ychp88NXWfFRFlmi1bst/xynOutbnpEfRCT9JZkZ7KOmF/I/hNKo2dILiMGnqblm+j1sggdLB9g==", 231 | "dependencies": { 232 | "Microsoft.Bcl.AsyncInterfaces": "6.0.0", 233 | "System.Buffers": "4.5.1", 234 | "System.Memory": "4.5.4", 235 | "System.Numerics.Vectors": "4.5.0", 236 | "System.Runtime.CompilerServices.Unsafe": "6.0.0", 237 | "System.Text.Encodings.Web": "6.0.0", 238 | "System.Threading.Tasks.Extensions": "4.5.4", 239 | "System.ValueTuple": "4.5.0" 240 | } 241 | }, 242 | "System.Threading.Tasks.Extensions": { 243 | "type": "Transitive", 244 | "resolved": "4.5.4", 245 | "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", 246 | "dependencies": { 247 | "System.Runtime.CompilerServices.Unsafe": "4.5.3" 248 | } 249 | }, 250 | "System.ValueTuple": { 251 | "type": "Transitive", 252 | "resolved": "4.5.0", 253 | "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" 254 | } 255 | }, 256 | ".NETStandard,Version=v2.0": { 257 | "Azure.Messaging.ServiceBus": { 258 | "type": "Direct", 259 | "requested": "[7.19.0, )", 260 | "resolved": "7.19.0", 261 | "contentHash": "DoGfZpH6bwtsA6siIS5wv3SZZzOoNLnJi9N7OHhtBvE5Wlfn13jvwEe5z92HOddJVYEchdIZBK+jHBtPz1HFpg==", 262 | "dependencies": { 263 | "Azure.Core": "1.44.1", 264 | "Azure.Core.Amqp": "1.3.1", 265 | "Microsoft.Azure.Amqp": "2.6.9" 266 | } 267 | }, 268 | "Hangfire.Core": { 269 | "type": "Direct", 270 | "requested": "[1.7.0, )", 271 | "resolved": "1.7.0", 272 | "contentHash": "ZWfWcE+kOWjKy7l/rsVtZmxs5XdltJY5ndx+vy7enD85ZVgc0Z+nIOZ/PfBhQQ68vGOfwOudpQVHuwXlSn7/fQ==", 273 | "dependencies": { 274 | "Newtonsoft.Json": "11.0.1" 275 | } 276 | }, 277 | "Hangfire.SqlServer": { 278 | "type": "Direct", 279 | "requested": "[1.7.0, )", 280 | "resolved": "1.7.0", 281 | "contentHash": "2gWcsGu2ws16mAml2FPwabdfk/NI+Ap+xLBU2geCvi+1vFA0SiQVW67GB9Iv6JzaxCqq88JUsHMAzJPwYkJM4A==", 282 | "dependencies": { 283 | "Hangfire.Core": "[1.7.0]", 284 | "System.Data.SqlClient": "4.4.0" 285 | } 286 | }, 287 | "Microsoft.CodeAnalysis.NetAnalyzers": { 288 | "type": "Direct", 289 | "requested": "[9.0.0, )", 290 | "resolved": "9.0.0", 291 | "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" 292 | }, 293 | "Microsoft.SourceLink.GitHub": { 294 | "type": "Direct", 295 | "requested": "[8.0.0, )", 296 | "resolved": "8.0.0", 297 | "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", 298 | "dependencies": { 299 | "Microsoft.Build.Tasks.Git": "8.0.0", 300 | "Microsoft.SourceLink.Common": "8.0.0" 301 | } 302 | }, 303 | "NETStandard.Library": { 304 | "type": "Direct", 305 | "requested": "[2.0.3, )", 306 | "resolved": "2.0.3", 307 | "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", 308 | "dependencies": { 309 | "Microsoft.NETCore.Platforms": "1.1.0" 310 | } 311 | }, 312 | "Azure.Core": { 313 | "type": "Transitive", 314 | "resolved": "1.44.1", 315 | "contentHash": "YyznXLQZCregzHvioip07/BkzjuWNXogJEVz9T5W6TwjNr17ax41YGzYMptlo2G10oLCuVPoyva62y0SIRDixg==", 316 | "dependencies": { 317 | "Microsoft.Bcl.AsyncInterfaces": "6.0.0", 318 | "System.ClientModel": "1.1.0", 319 | "System.Diagnostics.DiagnosticSource": "6.0.1", 320 | "System.Memory.Data": "6.0.0", 321 | "System.Numerics.Vectors": "4.5.0", 322 | "System.Text.Encodings.Web": "6.0.0", 323 | "System.Text.Json": "6.0.10", 324 | "System.Threading.Tasks.Extensions": "4.5.4" 325 | } 326 | }, 327 | "Azure.Core.Amqp": { 328 | "type": "Transitive", 329 | "resolved": "1.3.1", 330 | "contentHash": "AY1ZM4WwLBb9L2WwQoWs7wS2XKYg83tp3yVVdgySdebGN0FuIszuEqCy3Nhv6qHpbkjx/NGuOTsUbF/oNGBgwA==", 331 | "dependencies": { 332 | "Microsoft.Azure.Amqp": "2.6.7", 333 | "System.Memory": "4.5.4", 334 | "System.Memory.Data": "1.0.2" 335 | } 336 | }, 337 | "Microsoft.Azure.Amqp": { 338 | "type": "Transitive", 339 | "resolved": "2.6.9", 340 | "contentHash": "5i9XzfqxK1H5IBl+OuOV1jwJdrOvi5RUwsZgVOryZm0GCzcM9NWPNRxzPAbsSeaR2T6+1gGvdT3vR+Vbha6KFQ==" 341 | }, 342 | "Microsoft.Bcl.AsyncInterfaces": { 343 | "type": "Transitive", 344 | "resolved": "6.0.0", 345 | "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==", 346 | "dependencies": { 347 | "System.Threading.Tasks.Extensions": "4.5.4" 348 | } 349 | }, 350 | "Microsoft.Build.Tasks.Git": { 351 | "type": "Transitive", 352 | "resolved": "8.0.0", 353 | "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" 354 | }, 355 | "Microsoft.NETCore.Platforms": { 356 | "type": "Transitive", 357 | "resolved": "1.1.0", 358 | "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" 359 | }, 360 | "Microsoft.SourceLink.Common": { 361 | "type": "Transitive", 362 | "resolved": "8.0.0", 363 | "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" 364 | }, 365 | "Microsoft.Win32.Registry": { 366 | "type": "Transitive", 367 | "resolved": "4.4.0", 368 | "contentHash": "dA36TlNVn/XfrZtmf0fiI/z1nd3Wfp2QVzTdj26pqgP9LFWq0i1hYEUAW50xUjGFYn1+/cP3KGuxT2Yn1OUNBQ==", 369 | "dependencies": { 370 | "System.Security.AccessControl": "4.4.0", 371 | "System.Security.Principal.Windows": "4.4.0" 372 | } 373 | }, 374 | "Newtonsoft.Json": { 375 | "type": "Transitive", 376 | "resolved": "11.0.1", 377 | "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" 378 | }, 379 | "runtime.native.System.Data.SqlClient.sni": { 380 | "type": "Transitive", 381 | "resolved": "4.4.0", 382 | "contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==", 383 | "dependencies": { 384 | "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", 385 | "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", 386 | "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" 387 | } 388 | }, 389 | "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { 390 | "type": "Transitive", 391 | "resolved": "4.4.0", 392 | "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" 393 | }, 394 | "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { 395 | "type": "Transitive", 396 | "resolved": "4.4.0", 397 | "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" 398 | }, 399 | "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { 400 | "type": "Transitive", 401 | "resolved": "4.4.0", 402 | "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" 403 | }, 404 | "System.Buffers": { 405 | "type": "Transitive", 406 | "resolved": "4.5.1", 407 | "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" 408 | }, 409 | "System.ClientModel": { 410 | "type": "Transitive", 411 | "resolved": "1.1.0", 412 | "contentHash": "UocOlCkxLZrG2CKMAAImPcldJTxeesHnHGHwhJ0pNlZEvEXcWKuQvVOER2/NiOkJGRJk978SNdw3j6/7O9H1lg==", 413 | "dependencies": { 414 | "System.Memory.Data": "1.0.2", 415 | "System.Text.Json": "6.0.9" 416 | } 417 | }, 418 | "System.Data.SqlClient": { 419 | "type": "Transitive", 420 | "resolved": "4.4.0", 421 | "contentHash": "fxb9ghn1k1Ua7FFdlvtiBOD4/PsQvD/fk2KnhS+FK7VC6OggEx6P+lP1P0+KMb5V2dqS1+FbR7HCenoqzJMNIA==", 422 | "dependencies": { 423 | "Microsoft.Win32.Registry": "4.4.0", 424 | "System.Diagnostics.DiagnosticSource": "4.4.1", 425 | "System.Security.Principal.Windows": "4.4.0", 426 | "System.Text.Encoding.CodePages": "4.4.0", 427 | "runtime.native.System.Data.SqlClient.sni": "4.4.0" 428 | } 429 | }, 430 | "System.Diagnostics.DiagnosticSource": { 431 | "type": "Transitive", 432 | "resolved": "6.0.1", 433 | "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", 434 | "dependencies": { 435 | "System.Memory": "4.5.4", 436 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 437 | } 438 | }, 439 | "System.Memory": { 440 | "type": "Transitive", 441 | "resolved": "4.5.4", 442 | "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", 443 | "dependencies": { 444 | "System.Buffers": "4.5.1", 445 | "System.Numerics.Vectors": "4.4.0", 446 | "System.Runtime.CompilerServices.Unsafe": "4.5.3" 447 | } 448 | }, 449 | "System.Memory.Data": { 450 | "type": "Transitive", 451 | "resolved": "6.0.0", 452 | "contentHash": "ntFHArH3I4Lpjf5m4DCXQHJuGwWPNVJPaAvM95Jy/u+2Yzt2ryiyIN04LAogkjP9DeRcEOiviAjQotfmPq/FrQ==", 453 | "dependencies": { 454 | "System.Memory": "4.5.4", 455 | "System.Text.Json": "6.0.0" 456 | } 457 | }, 458 | "System.Numerics.Vectors": { 459 | "type": "Transitive", 460 | "resolved": "4.5.0", 461 | "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" 462 | }, 463 | "System.Runtime.CompilerServices.Unsafe": { 464 | "type": "Transitive", 465 | "resolved": "6.0.0", 466 | "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" 467 | }, 468 | "System.Security.AccessControl": { 469 | "type": "Transitive", 470 | "resolved": "4.4.0", 471 | "contentHash": "2NRFPX/V81ucKQmqNgGBZrKGH/5ejsvivSGMRum0SMgPnJxwhuNkzVS1+7gC3R2X0f57CtwrPrXPPSe6nOp82g==", 472 | "dependencies": { 473 | "System.Security.Principal.Windows": "4.4.0" 474 | } 475 | }, 476 | "System.Security.Principal.Windows": { 477 | "type": "Transitive", 478 | "resolved": "4.4.0", 479 | "contentHash": "pP+AOzt1o3jESOuLmf52YQTF7H3Ng9hTnrOESQiqsnl2IbBh1HInsAMHYtoh75iUYV0OIkHmjvveraYB6zM97w==" 480 | }, 481 | "System.Text.Encoding.CodePages": { 482 | "type": "Transitive", 483 | "resolved": "4.4.0", 484 | "contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==" 485 | }, 486 | "System.Text.Encodings.Web": { 487 | "type": "Transitive", 488 | "resolved": "6.0.0", 489 | "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", 490 | "dependencies": { 491 | "System.Buffers": "4.5.1", 492 | "System.Memory": "4.5.4", 493 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 494 | } 495 | }, 496 | "System.Text.Json": { 497 | "type": "Transitive", 498 | "resolved": "6.0.10", 499 | "contentHash": "NSB0kDipxn2ychp88NXWfFRFlmi1bst/xynOutbnpEfRCT9JZkZ7KOmF/I/hNKo2dILiMGnqblm+j1sggdLB9g==", 500 | "dependencies": { 501 | "Microsoft.Bcl.AsyncInterfaces": "6.0.0", 502 | "System.Buffers": "4.5.1", 503 | "System.Memory": "4.5.4", 504 | "System.Numerics.Vectors": "4.5.0", 505 | "System.Runtime.CompilerServices.Unsafe": "6.0.0", 506 | "System.Text.Encodings.Web": "6.0.0", 507 | "System.Threading.Tasks.Extensions": "4.5.4" 508 | } 509 | }, 510 | "System.Threading.Tasks.Extensions": { 511 | "type": "Transitive", 512 | "resolved": "4.5.4", 513 | "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", 514 | "dependencies": { 515 | "System.Runtime.CompilerServices.Unsafe": "4.5.3" 516 | } 517 | } 518 | } 519 | } 520 | } -------------------------------------------------------------------------------- /src/SharedAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyProduct("Hangfire")] 6 | [assembly: AssemblyCopyright("Copyright © 2013-2015 Sergey Odinokov, Adam Barclay")] 7 | [assembly: AssemblyCulture("")] 8 | 9 | [assembly: ComVisible(false)] 10 | [assembly: CLSCompliant(false)] 11 | 12 | // Don't edit manually! Use `build.bat version` command instead! 13 | [assembly: AssemblyVersion("6.0.0")] 14 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/HangFire.Azure.ServiceBusQueue.Tests/HangFire.Azure.ServiceBusQueue.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net5.0 4 | Hangfire.Azure.ServiceBusQueue.Tests 5 | Hangfire.Azure.ServiceBusQueue.Tests 6 | Copyright © Hangfire.Azure.ServiceBusQueue authors 2017 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Always 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/HangFire.Azure.ServiceBusQueue.Tests/ServiceBusQueueJobQueueFacts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Azure.Messaging.ServiceBus.Administration; 6 | using Hangfire.Azure.ServiceBusQueue; 7 | using Hangfire.SqlServer; 8 | using Hangfire.Storage; 9 | using NUnit.Framework; 10 | 11 | namespace HangFire.Azure.ServiceBusQueue.Tests 12 | { 13 | public class ServiceBusQueueJobQueueFacts 14 | { 15 | private ServiceBusQueueOptions options; 16 | private ServiceBusQueueJobQueueProvider provider; 17 | 18 | private IPersistentJobQueue queue; 19 | private IPersistentJobQueueMonitoringApi monitor; 20 | 21 | [SetUp] 22 | public void SetUpQueue() 23 | { 24 | SetUpQueue(TimeSpan.FromSeconds(5)); 25 | } 26 | 27 | [TearDown] 28 | public async Task DeleteQueues() 29 | { 30 | var manager = options.Credential != null ? new ServiceBusAdministrationClient(options.ConnectionString, options.Credential) : new ServiceBusAdministrationClient(options.ConnectionString); 31 | 32 | foreach (var queueName in options.Queues) 33 | { 34 | await manager.DeleteQueueAsync(options.QueuePrefix + queueName); 35 | } 36 | } 37 | 38 | [Test] 39 | public void Dequeue_ThrowsOperationCanceled_WhenCancellationTokenIsSetAtTheBeginning() 40 | { 41 | // Arrange 42 | var cts = new CancellationTokenSource(); 43 | cts.Cancel(); 44 | 45 | // Act / Assert 46 | Assert.Throws( 47 | () => queue.Dequeue(options.Queues, cts.Token)); 48 | } 49 | 50 | [Test] 51 | public void Dequeue_ShouldWaitIndefinitely_WhenThereAreNoJobs() 52 | { 53 | // Arrange 54 | var cts = new CancellationTokenSource(200); 55 | 56 | // Act / Assert 57 | Assert.Throws( 58 | () => queue.Dequeue(options.Queues, cts.Token)); 59 | } 60 | 61 | [Test] 62 | public void Dequeue_ShouldFetchJobs_OnlyFromSpecifiedQueues() 63 | { 64 | // Arrange 65 | queue.Enqueue(null, null, options.Queues[0], "1"); 66 | 67 | // Act / Assert 68 | Assert.Throws( 69 | () => queue.Dequeue( 70 | options.Queues.Skip(1).ToArray(), // Exclude Queues[0] 71 | CreateTimingOutCancellationToken())); 72 | } 73 | 74 | [Test] 75 | public void Dequeue_ShouldFetchJobs_FromMultipleQueues() 76 | { 77 | // Arrange 78 | queue.Enqueue(null, null, options.Queues[0], "1"); 79 | queue.Enqueue(null, null, options.Queues[1], "2"); 80 | 81 | // Act / Assert 82 | var job1 = queue.Dequeue( 83 | options.Queues, 84 | CreateTimingOutCancellationToken()); 85 | 86 | var job2 = queue.Dequeue( 87 | options.Queues, 88 | CreateTimingOutCancellationToken()); 89 | 90 | Assert.That(job1.JobId, Is.EqualTo("1")); 91 | Assert.That(job2.JobId, Is.EqualTo("2")); 92 | } 93 | 94 | [Test] 95 | public void Dequeue_ShouldFetchJobs_In_Multithread() 96 | { 97 | // Arrange 98 | queue.Enqueue(null, null, options.Queues[0], "1"); 99 | queue.Enqueue(null, null, options.Queues[0], "2"); 100 | queue.Enqueue(null, null, options.Queues[0], "3"); 101 | queue.Enqueue(null, null, options.Queues[0], "4"); 102 | 103 | var task1 = Task.Run(() => queue.Dequeue(options.Queues, CreateTimingOutCancellationToken())); 104 | var task2 = Task.Run(() => queue.Dequeue(options.Queues, CreateTimingOutCancellationToken())); 105 | var task3 = Task.Run(() => queue.Dequeue(options.Queues, CreateTimingOutCancellationToken())); 106 | var task4 = Task.Run(() => queue.Dequeue(options.Queues, CreateTimingOutCancellationToken())); 107 | 108 | IFetchedJob[] job = Task.WhenAll(task1, task2, task3, task4).GetAwaiter().GetResult(); 109 | 110 | Assert.That(job[0].JobId, Is.AnyOf("1", "2", "3", "4")); 111 | Assert.That(job[1].JobId, Is.AnyOf("1", "2", "3", "4")); 112 | Assert.That(job[2].JobId, Is.AnyOf("1", "2", "3", "4")); 113 | Assert.That(job[3].JobId, Is.AnyOf("1", "2", "3", "4")); 114 | 115 | Assert.That(job[0].JobId, !Is.AnyOf(job[1].JobId, job[2].JobId, job[3].JobId)); 116 | Assert.That(job[1].JobId, !Is.AnyOf(job[0].JobId, job[2].JobId, job[3].JobId)); 117 | Assert.That(job[2].JobId, !Is.AnyOf(job[0].JobId, job[1].JobId, job[3].JobId)); 118 | Assert.That(job[3].JobId, !Is.AnyOf(job[0].JobId, job[1].JobId, job[2].JobId)); 119 | } 120 | 121 | [Test] 122 | public void Dequeue_ShouldFetchAJob_FromTheSpecifiedQueue() 123 | { 124 | // Arrange 125 | queue.Enqueue(null, null, options.Queues[0], "1"); 126 | 127 | // Act / Assert 128 | var job = queue.Dequeue(options.Queues, CreateTimingOutCancellationToken()); 129 | 130 | Assert.That(job.JobId, Is.EqualTo("1")); 131 | } 132 | 133 | [Test] 134 | public void Dequeue_Complete_Should_RemoveFromQueue() 135 | { 136 | // Arrange 137 | queue.Enqueue(null, null, options.Queues[0], "1"); 138 | 139 | // Act 140 | var job = queue.Dequeue(options.Queues, CreateTimingOutCancellationToken()); 141 | job.RemoveFromQueue(); 142 | 143 | // Assert 144 | var counts = monitor.GetEnqueuedAndFetchedCount(options.Queues[0]); 145 | 146 | Assert.That(counts.EnqueuedCount, Is.EqualTo(0)); 147 | } 148 | 149 | [Test] 150 | public void Dequeue_Requeue_ShouldAddBackToQueue() 151 | { 152 | // Arrange 153 | queue.Enqueue(null, null, options.Queues[0], "1"); 154 | 155 | // Act 156 | var job = queue.Dequeue(options.Queues, CreateTimingOutCancellationToken()); 157 | job.Requeue(); 158 | 159 | // Assert 160 | var counts = monitor.GetEnqueuedAndFetchedCount(options.Queues[0]); 161 | 162 | Assert.That(counts.EnqueuedCount, Is.EqualTo(1)); 163 | } 164 | 165 | [Test] 166 | [TestCase(5000, 10000)] 167 | [TestCase(5000, 1000)] 168 | [TestCase(5000, 2100)] 169 | [TestCase(500, 499)] 170 | [TestCase(50, 111)] 171 | // Ensure we keep the message alive during a long processing period. 172 | public void Dequeue_Complete_AfterLockTime_ShouldRemoveFromQueue(int lockTimeMs, int processingTimeMs) 173 | { 174 | // Arrange 175 | SetUpQueue(TimeSpan.FromMilliseconds(lockTimeMs)); 176 | 177 | queue.Enqueue(null, null, options.Queues[0], "1"); 178 | 179 | // Act 180 | var job = queue.Dequeue(options.Queues, CreateTimingOutCancellationToken()); 181 | 182 | Thread.Sleep(TimeSpan.FromMilliseconds(processingTimeMs)); 183 | job.RemoveFromQueue(); 184 | 185 | // Assert 186 | var counts = monitor.GetEnqueuedAndFetchedCount(options.Queues[0]); 187 | 188 | Assert.That(counts.EnqueuedCount, Is.EqualTo(0)); 189 | } 190 | 191 | private static CancellationToken CreateTimingOutCancellationToken() 192 | { 193 | var source = new CancellationTokenSource(TimeSpan.FromSeconds(10)); 194 | return source.Token; 195 | } 196 | 197 | private void SetUpQueue(TimeSpan lockDuration) 198 | { 199 | options = new TestServiceBusQueueOptions 200 | { 201 | Configure = d => d.LockDuration = lockDuration 202 | }; 203 | 204 | provider = new ServiceBusQueueJobQueueProvider(options); 205 | 206 | queue = provider.GetJobQueue(); 207 | monitor = provider.GetJobQueueMonitoringApi(); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /tests/HangFire.Azure.ServiceBusQueue.Tests/ServiceBusQueueMonitoringApiFacts.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Messaging.ServiceBus; 5 | using Azure.Messaging.ServiceBus.Administration; 6 | using Hangfire.Azure.ServiceBusQueue; 7 | using Hangfire.SqlServer; 8 | using NUnit.Framework; 9 | 10 | namespace HangFire.Azure.ServiceBusQueue.Tests 11 | { 12 | public class ServiceBusQueueMonitoringApiFacts 13 | { 14 | private ServiceBusQueueOptions options; 15 | private ServiceBusQueueJobQueueProvider provider; 16 | 17 | private IPersistentJobQueue queue; 18 | private IPersistentJobQueueMonitoringApi monitor; 19 | 20 | [SetUp] 21 | public void SetUpQueue() 22 | { 23 | options = new TestServiceBusQueueOptions(); 24 | provider = new ServiceBusQueueJobQueueProvider(options); 25 | queue = provider.GetJobQueue(); 26 | monitor = provider.GetJobQueueMonitoringApi(); 27 | } 28 | 29 | [TearDown] 30 | public async Task DeleteQueues() 31 | { 32 | var manager = options.Credential != null ? new ServiceBusAdministrationClient(options.ConnectionString, options.Credential) : new ServiceBusAdministrationClient(options.ConnectionString); 33 | 34 | foreach (var currentQueue in options.Queues) 35 | { 36 | await manager.DeleteQueueAsync(options.QueuePrefix + currentQueue); 37 | } 38 | } 39 | 40 | [Test] 41 | public void GetEnqueuedAndFetchedCount_WhenNoJobs_ShouldCountFromQueue() 42 | { 43 | // Arrange 44 | // Act 45 | var counts = monitor.GetEnqueuedAndFetchedCount(options.Queues[0]); 46 | 47 | // Assert 48 | Assert.That(counts.EnqueuedCount, Is.EqualTo(0)); 49 | Assert.That(counts.FetchedCount, Is.Null); 50 | } 51 | 52 | [Test] 53 | public void GetEnqueuedAndFetchedCount_WhenJobs_ShouldCountFromQueue() 54 | { 55 | // Arrange 56 | queue.Enqueue(null, null, options.Queues[0], "1234"); 57 | 58 | // Act 59 | var counts = monitor.GetEnqueuedAndFetchedCount(options.Queues[0]); 60 | 61 | // Assert 62 | Assert.That(counts.EnqueuedCount, Is.EqualTo(1)); 63 | Assert.That(counts.FetchedCount, Is.Null); 64 | } 65 | 66 | [Test] 67 | public async Task GetEnqueuedAndFetchedCount_WhenDeadlettered_ShouldIgnore() 68 | { 69 | // Arrange 70 | queue.Enqueue(null, null, options.Queues[0], "1234"); 71 | var job = (ServiceBusQueueFetchedJob)queue.Dequeue(options.Queues, default(CancellationToken)); 72 | 73 | 74 | var fieldInfo = job.GetType().GetTypeInfo().GetField("_client", BindingFlags.NonPublic | BindingFlags.Instance); 75 | var client = fieldInfo?.GetValue(job) as ServiceBusReceiver; 76 | 77 | await client.DeadLetterMessageAsync(job.Message); 78 | 79 | // Act 80 | var counts = monitor.GetEnqueuedAndFetchedCount(options.Queues[0]); 81 | 82 | // Assert 83 | Assert.That(counts.EnqueuedCount, Is.EqualTo(0)); 84 | Assert.That(counts.FetchedCount, Is.Null); 85 | } 86 | 87 | [Test] 88 | public void GetEnqueuedJobIds_WhenJobs_ShouldWorkPage1() 89 | { 90 | // Arrange 91 | queue.Enqueue(null, null, options.Queues[0], "1"); 92 | queue.Enqueue(null, null, options.Queues[0], "2"); 93 | queue.Enqueue(null, null, options.Queues[0], "3"); 94 | queue.Enqueue(null, null, options.Queues[0], "4"); 95 | 96 | // Act 97 | var counts = monitor.GetEnqueuedJobIds(options.Queues[0], 0, 5); 98 | 99 | // Assert/ 100 | Assert.That(counts, Is.EquivalentTo(new long[] { 1, 2, 3, 4 })); 101 | } 102 | 103 | [Test] 104 | public void GetEnqueuedJobIds_WhenJobs_ShouldWorkPage2() 105 | { 106 | // Arrange 107 | queue.Enqueue(null, null, options.Queues[0], "1"); 108 | queue.Enqueue(null, null, options.Queues[0], "2"); 109 | queue.Enqueue(null, null, options.Queues[0], "3"); 110 | queue.Enqueue(null, null, options.Queues[0], "4"); 111 | 112 | // Act 113 | var counts1 = monitor.GetEnqueuedJobIds(options.Queues[0], 0, 2); 114 | var counts2 = monitor.GetEnqueuedJobIds(options.Queues[0], 2, 2); 115 | 116 | // Assert/ 117 | Assert.That(counts1, Is.EquivalentTo(new long[] { 1, 2 })); 118 | Assert.That(counts2, Is.EquivalentTo(new long[] { 3, 4 })); 119 | } 120 | 121 | [Test] 122 | public void GetEnqueuedJobIds_WhenJobs_ShouldWorkWithLargeValues() 123 | { 124 | // Arrange 125 | for (var i = 0; i < 100; i++) 126 | { 127 | queue.Enqueue(null, null, options.Queues[0], i.ToString()); 128 | } 129 | 130 | // Act 131 | var counts = monitor.GetEnqueuedJobIds(options.Queues[0], 58, 2); 132 | 133 | // Assert/ 134 | Assert.That(counts, Is.EquivalentTo(new long[] { 58, 59 })); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /tests/HangFire.Azure.ServiceBusQueue.Tests/TestServiceBusQueueOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Hangfire.Azure.ServiceBusQueue; 4 | using Microsoft.Extensions.Configuration; 5 | using Azure.Identity; 6 | 7 | namespace HangFire.Azure.ServiceBusQueue.Tests 8 | { 9 | public class TestServiceBusQueueOptions : ServiceBusQueueOptions 10 | { 11 | public TestServiceBusQueueOptions() 12 | { 13 | var configBuilder = new ConfigurationBuilder() 14 | .SetBasePath(Directory.GetCurrentDirectory()) 15 | .AddJsonFile("appsettings.json").Build(); 16 | 17 | CheckAndCreateQueues = true; 18 | ConnectionString = configBuilder["BusConnectionString"]; 19 | Credential = configBuilder["UseDefaultAzureCredential"] == "true" ? new DefaultAzureCredential() : null; 20 | QueuePrefix = "hf-sb-tests-"; 21 | Queues = new[] { "test1", "test2", "test3" }; 22 | QueuePollInterval = TimeSpan.Zero; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/HangFire.Azure.ServiceBusQueue.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "BusConnectionString": "", 3 | "UseDefaultAzureCredential": "false" 4 | } 5 | -------------------------------------------------------------------------------- /tests/HangFire.Azure.ServiceBusQueue.Tests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETCoreApp,Version=v5.0": { 5 | "Azure.Identity": { 6 | "type": "Direct", 7 | "requested": "[1.13.2, )", 8 | "resolved": "1.13.2", 9 | "contentHash": "CngQVQELdzFmsGSWyGIPIUOCrII7nApMVWxVmJCKQQrWxRXcNquCsZ+njRJRnhFUfD+KMAhpjyRCaceE4EOL6A==", 10 | "dependencies": { 11 | "Azure.Core": "1.44.1", 12 | "Microsoft.Identity.Client": "4.67.2", 13 | "Microsoft.Identity.Client.Extensions.Msal": "4.67.2", 14 | "System.Memory": "4.5.5", 15 | "System.Text.Json": "6.0.10", 16 | "System.Threading.Tasks.Extensions": "4.5.4" 17 | } 18 | }, 19 | "Microsoft.Extensions.Configuration.Json": { 20 | "type": "Direct", 21 | "requested": "[3.1.0, )", 22 | "resolved": "3.1.0", 23 | "contentHash": "gBpBE1GoaCf1PKYC7u0Bd4mVZ/eR2bnOvn7u8GBXEy3JGar6sC3UVpVfTB9w+biLPtzcukZynBG9uchSBbLTNQ==", 24 | "dependencies": { 25 | "Microsoft.Extensions.Configuration": "3.1.0", 26 | "Microsoft.Extensions.Configuration.FileExtensions": "3.1.0" 27 | } 28 | }, 29 | "Microsoft.NET.Test.Sdk": { 30 | "type": "Direct", 31 | "requested": "[16.4.0, )", 32 | "resolved": "16.4.0", 33 | "contentHash": "gjjqS3rCzg4DrSQq4YwJwUUvc49HtXpXrZkW9fu5VG+K4X+Ztn4+UzolyML7wPnl/EAIufk4hu628bJB4WtpFg==", 34 | "dependencies": { 35 | "Microsoft.CodeCoverage": "16.4.0", 36 | "Microsoft.TestPlatform.TestHost": "16.4.0" 37 | } 38 | }, 39 | "NUnit": { 40 | "type": "Direct", 41 | "requested": "[3.13.1, )", 42 | "resolved": "3.13.1", 43 | "contentHash": "vWBvrSelmTYwqHWvO3dA63z7cOpaFR/3nJ9MMVLBkWeaWa7oiglPPm5g1h96B4i2XXqjFexxhR5MyMjmIJYPfg==", 44 | "dependencies": { 45 | "NETStandard.Library": "2.0.0" 46 | } 47 | }, 48 | "NUnit3TestAdapter": { 49 | "type": "Direct", 50 | "requested": "[3.16.0, )", 51 | "resolved": "3.16.0", 52 | "contentHash": "yK8Y7DYboIz1pSVvjPN4SGGzso8OW5ZZ06kqqkeF4AXKvAeNo2n1bm41mArKk94+lKrD5m6RaWKnvoFk/U+z5w==", 53 | "dependencies": { 54 | "Microsoft.DotNet.InternalAbstractions": "1.0.0", 55 | "System.ComponentModel.EventBasedAsync": "4.3.0", 56 | "System.ComponentModel.TypeConverter": "4.3.0", 57 | "System.Diagnostics.Process": "4.3.0", 58 | "System.Reflection": "4.3.0", 59 | "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", 60 | "System.Threading.Thread": "4.3.0", 61 | "System.Xml.XPath.XmlDocument": "4.3.0", 62 | "System.Xml.XmlDocument": "4.3.0" 63 | } 64 | }, 65 | "Azure.Core": { 66 | "type": "Transitive", 67 | "resolved": "1.44.1", 68 | "contentHash": "YyznXLQZCregzHvioip07/BkzjuWNXogJEVz9T5W6TwjNr17ax41YGzYMptlo2G10oLCuVPoyva62y0SIRDixg==", 69 | "dependencies": { 70 | "Microsoft.Bcl.AsyncInterfaces": "6.0.0", 71 | "System.ClientModel": "1.1.0", 72 | "System.Diagnostics.DiagnosticSource": "6.0.1", 73 | "System.Memory.Data": "6.0.0", 74 | "System.Numerics.Vectors": "4.5.0", 75 | "System.Text.Encodings.Web": "6.0.0", 76 | "System.Text.Json": "6.0.10", 77 | "System.Threading.Tasks.Extensions": "4.5.4" 78 | } 79 | }, 80 | "Azure.Core.Amqp": { 81 | "type": "Transitive", 82 | "resolved": "1.3.1", 83 | "contentHash": "AY1ZM4WwLBb9L2WwQoWs7wS2XKYg83tp3yVVdgySdebGN0FuIszuEqCy3Nhv6qHpbkjx/NGuOTsUbF/oNGBgwA==", 84 | "dependencies": { 85 | "Microsoft.Azure.Amqp": "2.6.7", 86 | "System.Memory": "4.5.4", 87 | "System.Memory.Data": "1.0.2" 88 | } 89 | }, 90 | "Azure.Messaging.ServiceBus": { 91 | "type": "Transitive", 92 | "resolved": "7.19.0", 93 | "contentHash": "DoGfZpH6bwtsA6siIS5wv3SZZzOoNLnJi9N7OHhtBvE5Wlfn13jvwEe5z92HOddJVYEchdIZBK+jHBtPz1HFpg==", 94 | "dependencies": { 95 | "Azure.Core": "1.44.1", 96 | "Azure.Core.Amqp": "1.3.1", 97 | "Microsoft.Azure.Amqp": "2.6.9" 98 | } 99 | }, 100 | "Hangfire.Core": { 101 | "type": "Transitive", 102 | "resolved": "1.7.0", 103 | "contentHash": "ZWfWcE+kOWjKy7l/rsVtZmxs5XdltJY5ndx+vy7enD85ZVgc0Z+nIOZ/PfBhQQ68vGOfwOudpQVHuwXlSn7/fQ==", 104 | "dependencies": { 105 | "Newtonsoft.Json": "11.0.1" 106 | } 107 | }, 108 | "Hangfire.SqlServer": { 109 | "type": "Transitive", 110 | "resolved": "1.7.0", 111 | "contentHash": "2gWcsGu2ws16mAml2FPwabdfk/NI+Ap+xLBU2geCvi+1vFA0SiQVW67GB9Iv6JzaxCqq88JUsHMAzJPwYkJM4A==", 112 | "dependencies": { 113 | "Hangfire.Core": "[1.7.0]", 114 | "System.Data.SqlClient": "4.4.0" 115 | } 116 | }, 117 | "Microsoft.Azure.Amqp": { 118 | "type": "Transitive", 119 | "resolved": "2.6.9", 120 | "contentHash": "5i9XzfqxK1H5IBl+OuOV1jwJdrOvi5RUwsZgVOryZm0GCzcM9NWPNRxzPAbsSeaR2T6+1gGvdT3vR+Vbha6KFQ==" 121 | }, 122 | "Microsoft.Bcl.AsyncInterfaces": { 123 | "type": "Transitive", 124 | "resolved": "6.0.0", 125 | "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" 126 | }, 127 | "Microsoft.Build.Tasks.Git": { 128 | "type": "Transitive", 129 | "resolved": "8.0.0", 130 | "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" 131 | }, 132 | "Microsoft.CodeAnalysis.NetAnalyzers": { 133 | "type": "Transitive", 134 | "resolved": "9.0.0", 135 | "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" 136 | }, 137 | "Microsoft.CodeCoverage": { 138 | "type": "Transitive", 139 | "resolved": "16.4.0", 140 | "contentHash": "qb7PMVZMAY5iUCvB/kDUEt9xqazWKZoKY/sGpAJO4VtwgN5IcEgipCu3n0i1GPCwGbWVL6k4a8a9F+FqmADJng==" 141 | }, 142 | "Microsoft.DotNet.InternalAbstractions": { 143 | "type": "Transitive", 144 | "resolved": "1.0.0", 145 | "contentHash": "AAguUq7YyKk3yDWPoWA8DrLZvURxB/LrDdTn1h5lmPeznkFUpfC3p459w5mQYQE0qpquf/CkSQZ0etiV5vRHFA==", 146 | "dependencies": { 147 | "System.AppContext": "4.1.0", 148 | "System.Collections": "4.0.11", 149 | "System.IO": "4.1.0", 150 | "System.IO.FileSystem": "4.0.1", 151 | "System.Reflection.TypeExtensions": "4.1.0", 152 | "System.Runtime.Extensions": "4.1.0", 153 | "System.Runtime.InteropServices": "4.1.0", 154 | "System.Runtime.InteropServices.RuntimeInformation": "4.0.0" 155 | } 156 | }, 157 | "Microsoft.Extensions.Configuration": { 158 | "type": "Transitive", 159 | "resolved": "3.1.0", 160 | "contentHash": "Lu41BWNmwhKr6LgyQvcYBOge0pPvmiaK8R5UHXX4//wBhonJyWcT2OK1mqYfEM5G7pTf31fPrpIHOT6sN7EGOA==", 161 | "dependencies": { 162 | "Microsoft.Extensions.Configuration.Abstractions": "3.1.0" 163 | } 164 | }, 165 | "Microsoft.Extensions.Configuration.Abstractions": { 166 | "type": "Transitive", 167 | "resolved": "3.1.0", 168 | "contentHash": "ESz6bVoDQX7sgWdKHF6G9Pq672T8k+19AFb/txDXwdz7MoqaNQj2/in3agm/3qae9V+WvQZH86LLTNVo0it8vQ==", 169 | "dependencies": { 170 | "Microsoft.Extensions.Primitives": "3.1.0" 171 | } 172 | }, 173 | "Microsoft.Extensions.Configuration.FileExtensions": { 174 | "type": "Transitive", 175 | "resolved": "3.1.0", 176 | "contentHash": "OjRJIkVxUFiVkr9a39AqVThft9QHoef4But5pDCydJOXJ4D/SkmzuW1tm6J2IXynxj6qfeAz9QTnzQAvOcGvzg==", 177 | "dependencies": { 178 | "Microsoft.Extensions.Configuration": "3.1.0", 179 | "Microsoft.Extensions.FileProviders.Physical": "3.1.0" 180 | } 181 | }, 182 | "Microsoft.Extensions.FileProviders.Abstractions": { 183 | "type": "Transitive", 184 | "resolved": "3.1.0", 185 | "contentHash": "G3iBMOnn3tETEUvkE9J3a23wQpRkiXZp73zR0XNlicjLFhkeWW1FCaC2bTjrgHhPi2KO6x0BXnHvVuJPIlygBQ==", 186 | "dependencies": { 187 | "Microsoft.Extensions.Primitives": "3.1.0" 188 | } 189 | }, 190 | "Microsoft.Extensions.FileProviders.Physical": { 191 | "type": "Transitive", 192 | "resolved": "3.1.0", 193 | "contentHash": "KsvgrYp2fhNXoD9gqSu8jPK9Sbvaa7SqNtsLqHugJkCwFmgRvdz76z6Jz2tlFlC7wyMTZxwwtRF8WAorRQWTEA==", 194 | "dependencies": { 195 | "Microsoft.Extensions.FileProviders.Abstractions": "3.1.0", 196 | "Microsoft.Extensions.FileSystemGlobbing": "3.1.0" 197 | } 198 | }, 199 | "Microsoft.Extensions.FileSystemGlobbing": { 200 | "type": "Transitive", 201 | "resolved": "3.1.0", 202 | "contentHash": "tK5HZOmVv0kUYkonMjuSsxR0CBk+Rd/69QU3eOMv9FvODGZ2d0SR+7R+n8XIgBcCCoCHJBSsI4GPRaoN3Le4rA==" 203 | }, 204 | "Microsoft.Extensions.Primitives": { 205 | "type": "Transitive", 206 | "resolved": "3.1.0", 207 | "contentHash": "LEKAnX7lhUhSoIc2XraCTK3M4IU/LdVUzCe464Sa4+7F4ZJuXHHRzZli2mDbiT4xzAZhgqXbvfnb5+CNDcQFfg==" 208 | }, 209 | "Microsoft.Identity.Client": { 210 | "type": "Transitive", 211 | "resolved": "4.67.2", 212 | "contentHash": "37t0TfekfG6XM8kue/xNaA66Qjtti5Qe1xA41CK+bEd8VD76/oXJc+meFJHGzygIC485dCpKoamG/pDfb9Qd7Q==", 213 | "dependencies": { 214 | "Microsoft.IdentityModel.Abstractions": "6.35.0", 215 | "System.Diagnostics.DiagnosticSource": "6.0.1" 216 | } 217 | }, 218 | "Microsoft.Identity.Client.Extensions.Msal": { 219 | "type": "Transitive", 220 | "resolved": "4.67.2", 221 | "contentHash": "DKs+Lva6csEUZabw+JkkjtFgVmcXh4pJeQy5KH5XzPOaKNoZhAMYj1qpKd97qYTZKXIFH12bHPk0DA+6krw+Cw==", 222 | "dependencies": { 223 | "Microsoft.Identity.Client": "4.67.2", 224 | "System.IO.FileSystem.AccessControl": "5.0.0", 225 | "System.Security.Cryptography.ProtectedData": "4.5.0" 226 | } 227 | }, 228 | "Microsoft.IdentityModel.Abstractions": { 229 | "type": "Transitive", 230 | "resolved": "6.35.0", 231 | "contentHash": "xuR8E4Rd96M41CnUSCiOJ2DBh+z+zQSmyrYHdYhD6K4fXBcQGVnRCFQ0efROUYpP+p0zC1BLKr0JRpVuujTZSg==" 232 | }, 233 | "Microsoft.NETCore.Platforms": { 234 | "type": "Transitive", 235 | "resolved": "5.0.0", 236 | "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" 237 | }, 238 | "Microsoft.NETCore.Targets": { 239 | "type": "Transitive", 240 | "resolved": "1.1.0", 241 | "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" 242 | }, 243 | "Microsoft.SourceLink.Common": { 244 | "type": "Transitive", 245 | "resolved": "8.0.0", 246 | "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" 247 | }, 248 | "Microsoft.SourceLink.GitHub": { 249 | "type": "Transitive", 250 | "resolved": "8.0.0", 251 | "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", 252 | "dependencies": { 253 | "Microsoft.Build.Tasks.Git": "8.0.0", 254 | "Microsoft.SourceLink.Common": "8.0.0" 255 | } 256 | }, 257 | "Microsoft.TestPlatform.ObjectModel": { 258 | "type": "Transitive", 259 | "resolved": "16.4.0", 260 | "contentHash": "4geKywSUJHHrfBTr1wJXUVWP0Vx1X03oFQAdZdGa8jK8p5MSwsJ4Vd0/mqN0dB2YXaIXIhDT94ti5WQ1KZ4jdw==", 261 | "dependencies": { 262 | "NuGet.Frameworks": "5.0.0" 263 | } 264 | }, 265 | "Microsoft.TestPlatform.TestHost": { 266 | "type": "Transitive", 267 | "resolved": "16.4.0", 268 | "contentHash": "tMlz3uc7VUZSYYslyVOVXH40KexTptZMAi8gIE+5w+SIt8I0qLPObpqX6QVLWszPr7sxX4WAKTiFgqgBB1MxjA==", 269 | "dependencies": { 270 | "Microsoft.TestPlatform.ObjectModel": "16.4.0", 271 | "Newtonsoft.Json": "9.0.1" 272 | } 273 | }, 274 | "Microsoft.Win32.Primitives": { 275 | "type": "Transitive", 276 | "resolved": "4.3.0", 277 | "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", 278 | "dependencies": { 279 | "Microsoft.NETCore.Platforms": "1.1.0", 280 | "Microsoft.NETCore.Targets": "1.1.0", 281 | "System.Runtime": "4.3.0" 282 | } 283 | }, 284 | "Microsoft.Win32.Registry": { 285 | "type": "Transitive", 286 | "resolved": "4.4.0", 287 | "contentHash": "dA36TlNVn/XfrZtmf0fiI/z1nd3Wfp2QVzTdj26pqgP9LFWq0i1hYEUAW50xUjGFYn1+/cP3KGuxT2Yn1OUNBQ==", 288 | "dependencies": { 289 | "Microsoft.NETCore.Platforms": "2.0.0", 290 | "System.Security.AccessControl": "4.4.0", 291 | "System.Security.Principal.Windows": "4.4.0" 292 | } 293 | }, 294 | "NETStandard.Library": { 295 | "type": "Transitive", 296 | "resolved": "2.0.0", 297 | "contentHash": "7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", 298 | "dependencies": { 299 | "Microsoft.NETCore.Platforms": "1.1.0" 300 | } 301 | }, 302 | "Newtonsoft.Json": { 303 | "type": "Transitive", 304 | "resolved": "11.0.1", 305 | "contentHash": "pNN4l+J6LlpIvHOeNdXlwxv39NPJ2B5klz+Rd2UQZIx30Squ5oND1Yy3wEAUoKn0GPUj6Yxt9lxlYWQqfZcvKg==" 306 | }, 307 | "NuGet.Frameworks": { 308 | "type": "Transitive", 309 | "resolved": "5.0.0", 310 | "contentHash": "c5JVjuVAm4f7E9Vj+v09Z9s2ZsqFDjBpcsyS3M9xRo0bEdm/LVZSzLxxNvfvAwRiiE8nwe1h2G4OwiwlzFKXlA==" 311 | }, 312 | "runtime.native.System": { 313 | "type": "Transitive", 314 | "resolved": "4.3.0", 315 | "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", 316 | "dependencies": { 317 | "Microsoft.NETCore.Platforms": "1.1.0", 318 | "Microsoft.NETCore.Targets": "1.1.0" 319 | } 320 | }, 321 | "runtime.native.System.Data.SqlClient.sni": { 322 | "type": "Transitive", 323 | "resolved": "4.4.0", 324 | "contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==", 325 | "dependencies": { 326 | "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", 327 | "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", 328 | "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" 329 | } 330 | }, 331 | "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { 332 | "type": "Transitive", 333 | "resolved": "4.4.0", 334 | "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" 335 | }, 336 | "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { 337 | "type": "Transitive", 338 | "resolved": "4.4.0", 339 | "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" 340 | }, 341 | "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { 342 | "type": "Transitive", 343 | "resolved": "4.4.0", 344 | "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" 345 | }, 346 | "System.AppContext": { 347 | "type": "Transitive", 348 | "resolved": "4.1.0", 349 | "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", 350 | "dependencies": { 351 | "System.Runtime": "4.1.0" 352 | } 353 | }, 354 | "System.ClientModel": { 355 | "type": "Transitive", 356 | "resolved": "1.1.0", 357 | "contentHash": "UocOlCkxLZrG2CKMAAImPcldJTxeesHnHGHwhJ0pNlZEvEXcWKuQvVOER2/NiOkJGRJk978SNdw3j6/7O9H1lg==", 358 | "dependencies": { 359 | "System.Memory.Data": "1.0.2", 360 | "System.Text.Json": "6.0.9" 361 | } 362 | }, 363 | "System.Collections": { 364 | "type": "Transitive", 365 | "resolved": "4.3.0", 366 | "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", 367 | "dependencies": { 368 | "Microsoft.NETCore.Platforms": "1.1.0", 369 | "Microsoft.NETCore.Targets": "1.1.0", 370 | "System.Runtime": "4.3.0" 371 | } 372 | }, 373 | "System.Collections.NonGeneric": { 374 | "type": "Transitive", 375 | "resolved": "4.3.0", 376 | "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", 377 | "dependencies": { 378 | "System.Diagnostics.Debug": "4.3.0", 379 | "System.Globalization": "4.3.0", 380 | "System.Resources.ResourceManager": "4.3.0", 381 | "System.Runtime": "4.3.0", 382 | "System.Runtime.Extensions": "4.3.0", 383 | "System.Threading": "4.3.0" 384 | } 385 | }, 386 | "System.Collections.Specialized": { 387 | "type": "Transitive", 388 | "resolved": "4.3.0", 389 | "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", 390 | "dependencies": { 391 | "System.Collections.NonGeneric": "4.3.0", 392 | "System.Globalization": "4.3.0", 393 | "System.Globalization.Extensions": "4.3.0", 394 | "System.Resources.ResourceManager": "4.3.0", 395 | "System.Runtime": "4.3.0", 396 | "System.Runtime.Extensions": "4.3.0", 397 | "System.Threading": "4.3.0" 398 | } 399 | }, 400 | "System.ComponentModel": { 401 | "type": "Transitive", 402 | "resolved": "4.3.0", 403 | "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", 404 | "dependencies": { 405 | "System.Runtime": "4.3.0" 406 | } 407 | }, 408 | "System.ComponentModel.EventBasedAsync": { 409 | "type": "Transitive", 410 | "resolved": "4.3.0", 411 | "contentHash": "fCFl8f0XdwA/BuoNrVBB5D0Y48/hv2J+w4xSDdXQitXZsR6UCSOrDVE7TCUraY802ENwcHUnUCv4En8CupDU1g==", 412 | "dependencies": { 413 | "System.Resources.ResourceManager": "4.3.0", 414 | "System.Runtime": "4.3.0", 415 | "System.Threading": "4.3.0", 416 | "System.Threading.Tasks": "4.3.0" 417 | } 418 | }, 419 | "System.ComponentModel.Primitives": { 420 | "type": "Transitive", 421 | "resolved": "4.3.0", 422 | "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", 423 | "dependencies": { 424 | "System.ComponentModel": "4.3.0", 425 | "System.Resources.ResourceManager": "4.3.0", 426 | "System.Runtime": "4.3.0" 427 | } 428 | }, 429 | "System.ComponentModel.TypeConverter": { 430 | "type": "Transitive", 431 | "resolved": "4.3.0", 432 | "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", 433 | "dependencies": { 434 | "System.Collections": "4.3.0", 435 | "System.Collections.NonGeneric": "4.3.0", 436 | "System.Collections.Specialized": "4.3.0", 437 | "System.ComponentModel": "4.3.0", 438 | "System.ComponentModel.Primitives": "4.3.0", 439 | "System.Globalization": "4.3.0", 440 | "System.Linq": "4.3.0", 441 | "System.Reflection": "4.3.0", 442 | "System.Reflection.Extensions": "4.3.0", 443 | "System.Reflection.Primitives": "4.3.0", 444 | "System.Reflection.TypeExtensions": "4.3.0", 445 | "System.Resources.ResourceManager": "4.3.0", 446 | "System.Runtime": "4.3.0", 447 | "System.Runtime.Extensions": "4.3.0", 448 | "System.Threading": "4.3.0" 449 | } 450 | }, 451 | "System.Data.SqlClient": { 452 | "type": "Transitive", 453 | "resolved": "4.4.0", 454 | "contentHash": "fxb9ghn1k1Ua7FFdlvtiBOD4/PsQvD/fk2KnhS+FK7VC6OggEx6P+lP1P0+KMb5V2dqS1+FbR7HCenoqzJMNIA==", 455 | "dependencies": { 456 | "Microsoft.Win32.Registry": "4.4.0", 457 | "System.Security.Principal.Windows": "4.4.0", 458 | "System.Text.Encoding.CodePages": "4.4.0", 459 | "runtime.native.System.Data.SqlClient.sni": "4.4.0" 460 | } 461 | }, 462 | "System.Diagnostics.Debug": { 463 | "type": "Transitive", 464 | "resolved": "4.3.0", 465 | "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", 466 | "dependencies": { 467 | "Microsoft.NETCore.Platforms": "1.1.0", 468 | "Microsoft.NETCore.Targets": "1.1.0", 469 | "System.Runtime": "4.3.0" 470 | } 471 | }, 472 | "System.Diagnostics.DiagnosticSource": { 473 | "type": "Transitive", 474 | "resolved": "6.0.1", 475 | "contentHash": "KiLYDu2k2J82Q9BJpWiuQqCkFjRBWVq4jDzKKWawVi9KWzyD0XG3cmfX0vqTQlL14Wi9EufJrbL0+KCLTbqWiQ==", 476 | "dependencies": { 477 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 478 | } 479 | }, 480 | "System.Diagnostics.Process": { 481 | "type": "Transitive", 482 | "resolved": "4.3.0", 483 | "contentHash": "J0wOX07+QASQblsfxmIMFc9Iq7KTXYL3zs2G/Xc704Ylv3NpuVdo6gij6V3PGiptTxqsK0K7CdXenRvKUnkA2g==", 484 | "dependencies": { 485 | "Microsoft.NETCore.Platforms": "1.1.0", 486 | "Microsoft.Win32.Primitives": "4.3.0", 487 | "Microsoft.Win32.Registry": "4.3.0", 488 | "System.Collections": "4.3.0", 489 | "System.Diagnostics.Debug": "4.3.0", 490 | "System.Globalization": "4.3.0", 491 | "System.IO": "4.3.0", 492 | "System.IO.FileSystem": "4.3.0", 493 | "System.IO.FileSystem.Primitives": "4.3.0", 494 | "System.Resources.ResourceManager": "4.3.0", 495 | "System.Runtime": "4.3.0", 496 | "System.Runtime.Extensions": "4.3.0", 497 | "System.Runtime.Handles": "4.3.0", 498 | "System.Runtime.InteropServices": "4.3.0", 499 | "System.Text.Encoding": "4.3.0", 500 | "System.Text.Encoding.Extensions": "4.3.0", 501 | "System.Threading": "4.3.0", 502 | "System.Threading.Tasks": "4.3.0", 503 | "System.Threading.Thread": "4.3.0", 504 | "System.Threading.ThreadPool": "4.3.0", 505 | "runtime.native.System": "4.3.0" 506 | } 507 | }, 508 | "System.Globalization": { 509 | "type": "Transitive", 510 | "resolved": "4.3.0", 511 | "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", 512 | "dependencies": { 513 | "Microsoft.NETCore.Platforms": "1.1.0", 514 | "Microsoft.NETCore.Targets": "1.1.0", 515 | "System.Runtime": "4.3.0" 516 | } 517 | }, 518 | "System.Globalization.Extensions": { 519 | "type": "Transitive", 520 | "resolved": "4.3.0", 521 | "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", 522 | "dependencies": { 523 | "Microsoft.NETCore.Platforms": "1.1.0", 524 | "System.Globalization": "4.3.0", 525 | "System.Resources.ResourceManager": "4.3.0", 526 | "System.Runtime": "4.3.0", 527 | "System.Runtime.Extensions": "4.3.0", 528 | "System.Runtime.InteropServices": "4.3.0" 529 | } 530 | }, 531 | "System.IO": { 532 | "type": "Transitive", 533 | "resolved": "4.3.0", 534 | "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", 535 | "dependencies": { 536 | "Microsoft.NETCore.Platforms": "1.1.0", 537 | "Microsoft.NETCore.Targets": "1.1.0", 538 | "System.Runtime": "4.3.0", 539 | "System.Text.Encoding": "4.3.0", 540 | "System.Threading.Tasks": "4.3.0" 541 | } 542 | }, 543 | "System.IO.FileSystem": { 544 | "type": "Transitive", 545 | "resolved": "4.3.0", 546 | "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", 547 | "dependencies": { 548 | "Microsoft.NETCore.Platforms": "1.1.0", 549 | "Microsoft.NETCore.Targets": "1.1.0", 550 | "System.IO": "4.3.0", 551 | "System.IO.FileSystem.Primitives": "4.3.0", 552 | "System.Runtime": "4.3.0", 553 | "System.Runtime.Handles": "4.3.0", 554 | "System.Text.Encoding": "4.3.0", 555 | "System.Threading.Tasks": "4.3.0" 556 | } 557 | }, 558 | "System.IO.FileSystem.AccessControl": { 559 | "type": "Transitive", 560 | "resolved": "5.0.0", 561 | "contentHash": "SxHB3nuNrpptVk+vZ/F+7OHEpoHUIKKMl02bUmYHQr1r+glbZQxs7pRtsf4ENO29TVm2TH3AEeep2fJcy92oYw==", 562 | "dependencies": { 563 | "System.Security.AccessControl": "5.0.0", 564 | "System.Security.Principal.Windows": "5.0.0" 565 | } 566 | }, 567 | "System.IO.FileSystem.Primitives": { 568 | "type": "Transitive", 569 | "resolved": "4.3.0", 570 | "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", 571 | "dependencies": { 572 | "System.Runtime": "4.3.0" 573 | } 574 | }, 575 | "System.Linq": { 576 | "type": "Transitive", 577 | "resolved": "4.3.0", 578 | "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", 579 | "dependencies": { 580 | "System.Collections": "4.3.0", 581 | "System.Diagnostics.Debug": "4.3.0", 582 | "System.Resources.ResourceManager": "4.3.0", 583 | "System.Runtime": "4.3.0", 584 | "System.Runtime.Extensions": "4.3.0" 585 | } 586 | }, 587 | "System.Memory": { 588 | "type": "Transitive", 589 | "resolved": "4.5.5", 590 | "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" 591 | }, 592 | "System.Memory.Data": { 593 | "type": "Transitive", 594 | "resolved": "6.0.0", 595 | "contentHash": "ntFHArH3I4Lpjf5m4DCXQHJuGwWPNVJPaAvM95Jy/u+2Yzt2ryiyIN04LAogkjP9DeRcEOiviAjQotfmPq/FrQ==", 596 | "dependencies": { 597 | "System.Memory": "4.5.4", 598 | "System.Text.Json": "6.0.0" 599 | } 600 | }, 601 | "System.Numerics.Vectors": { 602 | "type": "Transitive", 603 | "resolved": "4.5.0", 604 | "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" 605 | }, 606 | "System.Reflection": { 607 | "type": "Transitive", 608 | "resolved": "4.3.0", 609 | "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", 610 | "dependencies": { 611 | "Microsoft.NETCore.Platforms": "1.1.0", 612 | "Microsoft.NETCore.Targets": "1.1.0", 613 | "System.IO": "4.3.0", 614 | "System.Reflection.Primitives": "4.3.0", 615 | "System.Runtime": "4.3.0" 616 | } 617 | }, 618 | "System.Reflection.Extensions": { 619 | "type": "Transitive", 620 | "resolved": "4.3.0", 621 | "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", 622 | "dependencies": { 623 | "Microsoft.NETCore.Platforms": "1.1.0", 624 | "Microsoft.NETCore.Targets": "1.1.0", 625 | "System.Reflection": "4.3.0", 626 | "System.Runtime": "4.3.0" 627 | } 628 | }, 629 | "System.Reflection.Primitives": { 630 | "type": "Transitive", 631 | "resolved": "4.3.0", 632 | "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", 633 | "dependencies": { 634 | "Microsoft.NETCore.Platforms": "1.1.0", 635 | "Microsoft.NETCore.Targets": "1.1.0", 636 | "System.Runtime": "4.3.0" 637 | } 638 | }, 639 | "System.Reflection.TypeExtensions": { 640 | "type": "Transitive", 641 | "resolved": "4.3.0", 642 | "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", 643 | "dependencies": { 644 | "System.Reflection": "4.3.0", 645 | "System.Runtime": "4.3.0" 646 | } 647 | }, 648 | "System.Resources.ResourceManager": { 649 | "type": "Transitive", 650 | "resolved": "4.3.0", 651 | "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", 652 | "dependencies": { 653 | "Microsoft.NETCore.Platforms": "1.1.0", 654 | "Microsoft.NETCore.Targets": "1.1.0", 655 | "System.Globalization": "4.3.0", 656 | "System.Reflection": "4.3.0", 657 | "System.Runtime": "4.3.0" 658 | } 659 | }, 660 | "System.Runtime": { 661 | "type": "Transitive", 662 | "resolved": "4.3.0", 663 | "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", 664 | "dependencies": { 665 | "Microsoft.NETCore.Platforms": "1.1.0", 666 | "Microsoft.NETCore.Targets": "1.1.0" 667 | } 668 | }, 669 | "System.Runtime.CompilerServices.Unsafe": { 670 | "type": "Transitive", 671 | "resolved": "6.0.0", 672 | "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" 673 | }, 674 | "System.Runtime.Extensions": { 675 | "type": "Transitive", 676 | "resolved": "4.3.0", 677 | "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", 678 | "dependencies": { 679 | "Microsoft.NETCore.Platforms": "1.1.0", 680 | "Microsoft.NETCore.Targets": "1.1.0", 681 | "System.Runtime": "4.3.0" 682 | } 683 | }, 684 | "System.Runtime.Handles": { 685 | "type": "Transitive", 686 | "resolved": "4.3.0", 687 | "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", 688 | "dependencies": { 689 | "Microsoft.NETCore.Platforms": "1.1.0", 690 | "Microsoft.NETCore.Targets": "1.1.0", 691 | "System.Runtime": "4.3.0" 692 | } 693 | }, 694 | "System.Runtime.InteropServices": { 695 | "type": "Transitive", 696 | "resolved": "4.3.0", 697 | "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", 698 | "dependencies": { 699 | "Microsoft.NETCore.Platforms": "1.1.0", 700 | "Microsoft.NETCore.Targets": "1.1.0", 701 | "System.Reflection": "4.3.0", 702 | "System.Reflection.Primitives": "4.3.0", 703 | "System.Runtime": "4.3.0", 704 | "System.Runtime.Handles": "4.3.0" 705 | } 706 | }, 707 | "System.Runtime.InteropServices.RuntimeInformation": { 708 | "type": "Transitive", 709 | "resolved": "4.3.0", 710 | "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", 711 | "dependencies": { 712 | "System.Reflection": "4.3.0", 713 | "System.Reflection.Extensions": "4.3.0", 714 | "System.Resources.ResourceManager": "4.3.0", 715 | "System.Runtime": "4.3.0", 716 | "System.Runtime.InteropServices": "4.3.0", 717 | "System.Threading": "4.3.0", 718 | "runtime.native.System": "4.3.0" 719 | } 720 | }, 721 | "System.Security.AccessControl": { 722 | "type": "Transitive", 723 | "resolved": "5.0.0", 724 | "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", 725 | "dependencies": { 726 | "Microsoft.NETCore.Platforms": "5.0.0", 727 | "System.Security.Principal.Windows": "5.0.0" 728 | } 729 | }, 730 | "System.Security.Cryptography.ProtectedData": { 731 | "type": "Transitive", 732 | "resolved": "4.5.0", 733 | "contentHash": "wLBKzFnDCxP12VL9ANydSYhk59fC4cvOr9ypYQLPnAj48NQIhqnjdD2yhP8yEKyBJEjERWS9DisKL7rX5eU25Q==" 734 | }, 735 | "System.Security.Principal.Windows": { 736 | "type": "Transitive", 737 | "resolved": "5.0.0", 738 | "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" 739 | }, 740 | "System.Text.Encoding": { 741 | "type": "Transitive", 742 | "resolved": "4.3.0", 743 | "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", 744 | "dependencies": { 745 | "Microsoft.NETCore.Platforms": "1.1.0", 746 | "Microsoft.NETCore.Targets": "1.1.0", 747 | "System.Runtime": "4.3.0" 748 | } 749 | }, 750 | "System.Text.Encoding.CodePages": { 751 | "type": "Transitive", 752 | "resolved": "4.4.0", 753 | "contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==", 754 | "dependencies": { 755 | "Microsoft.NETCore.Platforms": "2.0.0" 756 | } 757 | }, 758 | "System.Text.Encoding.Extensions": { 759 | "type": "Transitive", 760 | "resolved": "4.3.0", 761 | "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", 762 | "dependencies": { 763 | "Microsoft.NETCore.Platforms": "1.1.0", 764 | "Microsoft.NETCore.Targets": "1.1.0", 765 | "System.Runtime": "4.3.0", 766 | "System.Text.Encoding": "4.3.0" 767 | } 768 | }, 769 | "System.Text.Encodings.Web": { 770 | "type": "Transitive", 771 | "resolved": "6.0.0", 772 | "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", 773 | "dependencies": { 774 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 775 | } 776 | }, 777 | "System.Text.Json": { 778 | "type": "Transitive", 779 | "resolved": "6.0.10", 780 | "contentHash": "NSB0kDipxn2ychp88NXWfFRFlmi1bst/xynOutbnpEfRCT9JZkZ7KOmF/I/hNKo2dILiMGnqblm+j1sggdLB9g==", 781 | "dependencies": { 782 | "System.Runtime.CompilerServices.Unsafe": "6.0.0", 783 | "System.Text.Encodings.Web": "6.0.0" 784 | } 785 | }, 786 | "System.Text.RegularExpressions": { 787 | "type": "Transitive", 788 | "resolved": "4.3.0", 789 | "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", 790 | "dependencies": { 791 | "System.Runtime": "4.3.0" 792 | } 793 | }, 794 | "System.Threading": { 795 | "type": "Transitive", 796 | "resolved": "4.3.0", 797 | "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", 798 | "dependencies": { 799 | "System.Runtime": "4.3.0", 800 | "System.Threading.Tasks": "4.3.0" 801 | } 802 | }, 803 | "System.Threading.Tasks": { 804 | "type": "Transitive", 805 | "resolved": "4.3.0", 806 | "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", 807 | "dependencies": { 808 | "Microsoft.NETCore.Platforms": "1.1.0", 809 | "Microsoft.NETCore.Targets": "1.1.0", 810 | "System.Runtime": "4.3.0" 811 | } 812 | }, 813 | "System.Threading.Tasks.Extensions": { 814 | "type": "Transitive", 815 | "resolved": "4.5.4", 816 | "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" 817 | }, 818 | "System.Threading.Thread": { 819 | "type": "Transitive", 820 | "resolved": "4.3.0", 821 | "contentHash": "OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", 822 | "dependencies": { 823 | "System.Runtime": "4.3.0" 824 | } 825 | }, 826 | "System.Threading.ThreadPool": { 827 | "type": "Transitive", 828 | "resolved": "4.3.0", 829 | "contentHash": "k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", 830 | "dependencies": { 831 | "System.Runtime": "4.3.0", 832 | "System.Runtime.Handles": "4.3.0" 833 | } 834 | }, 835 | "System.Xml.ReaderWriter": { 836 | "type": "Transitive", 837 | "resolved": "4.3.0", 838 | "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", 839 | "dependencies": { 840 | "System.Collections": "4.3.0", 841 | "System.Diagnostics.Debug": "4.3.0", 842 | "System.Globalization": "4.3.0", 843 | "System.IO": "4.3.0", 844 | "System.IO.FileSystem": "4.3.0", 845 | "System.IO.FileSystem.Primitives": "4.3.0", 846 | "System.Resources.ResourceManager": "4.3.0", 847 | "System.Runtime": "4.3.0", 848 | "System.Runtime.Extensions": "4.3.0", 849 | "System.Runtime.InteropServices": "4.3.0", 850 | "System.Text.Encoding": "4.3.0", 851 | "System.Text.Encoding.Extensions": "4.3.0", 852 | "System.Text.RegularExpressions": "4.3.0", 853 | "System.Threading.Tasks": "4.3.0", 854 | "System.Threading.Tasks.Extensions": "4.3.0" 855 | } 856 | }, 857 | "System.Xml.XmlDocument": { 858 | "type": "Transitive", 859 | "resolved": "4.3.0", 860 | "contentHash": "lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", 861 | "dependencies": { 862 | "System.Collections": "4.3.0", 863 | "System.Diagnostics.Debug": "4.3.0", 864 | "System.Globalization": "4.3.0", 865 | "System.IO": "4.3.0", 866 | "System.Resources.ResourceManager": "4.3.0", 867 | "System.Runtime": "4.3.0", 868 | "System.Runtime.Extensions": "4.3.0", 869 | "System.Text.Encoding": "4.3.0", 870 | "System.Threading": "4.3.0", 871 | "System.Xml.ReaderWriter": "4.3.0" 872 | } 873 | }, 874 | "System.Xml.XPath": { 875 | "type": "Transitive", 876 | "resolved": "4.3.0", 877 | "contentHash": "v1JQ5SETnQusqmS3RwStF7vwQ3L02imIzl++sewmt23VGygix04pEH+FCj1yWb+z4GDzKiljr1W7Wfvrx0YwgA==", 878 | "dependencies": { 879 | "System.Collections": "4.3.0", 880 | "System.Diagnostics.Debug": "4.3.0", 881 | "System.Globalization": "4.3.0", 882 | "System.IO": "4.3.0", 883 | "System.Resources.ResourceManager": "4.3.0", 884 | "System.Runtime": "4.3.0", 885 | "System.Runtime.Extensions": "4.3.0", 886 | "System.Threading": "4.3.0", 887 | "System.Xml.ReaderWriter": "4.3.0" 888 | } 889 | }, 890 | "System.Xml.XPath.XmlDocument": { 891 | "type": "Transitive", 892 | "resolved": "4.3.0", 893 | "contentHash": "A/uxsWi/Ifzkmd4ArTLISMbfFs6XpRPsXZonrIqyTY70xi8t+mDtvSM5Os0RqyRDobjMBwIDHDL4NOIbkDwf7A==", 894 | "dependencies": { 895 | "System.Collections": "4.3.0", 896 | "System.Globalization": "4.3.0", 897 | "System.IO": "4.3.0", 898 | "System.Resources.ResourceManager": "4.3.0", 899 | "System.Runtime": "4.3.0", 900 | "System.Runtime.Extensions": "4.3.0", 901 | "System.Threading": "4.3.0", 902 | "System.Xml.ReaderWriter": "4.3.0", 903 | "System.Xml.XPath": "4.3.0", 904 | "System.Xml.XmlDocument": "4.3.0" 905 | } 906 | }, 907 | "hangfire.azure.servicebusqueue": { 908 | "type": "Project", 909 | "dependencies": { 910 | "Azure.Messaging.ServiceBus": "[7.19.0, )", 911 | "Hangfire.Core": "[1.7.0, )", 912 | "Hangfire.SqlServer": "[1.7.0, )", 913 | "Microsoft.CodeAnalysis.NetAnalyzers": "[9.0.0, )", 914 | "Microsoft.SourceLink.GitHub": "[8.0.0, )" 915 | } 916 | } 917 | } 918 | } 919 | } --------------------------------------------------------------------------------