├── .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 | [](http://hangfire.io) [](https://www.nuget.org/packages/HangFire.Azure.ServiceBusQueue/) [](https://ci.appveyor.com/project/odinserj/hangfire-azure-servicebusqueue) [](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 | }
--------------------------------------------------------------------------------