diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c4efe2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,261 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..418de4c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,174 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ab62036
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# Sample MassTransit Quartz Scheduler
+
+This sample contains three examples:
+
+1. Net461 using TopShelf
+2. NetCore Win Svc (for still deploying on a Windows Server)
+3. NetCore (linux, windows, mac...)
+
+The persistence mechanism used in this example is SQLServer, however Quartz.net supports [all of these](https://github.com/quartznet/quartznet/tree/master/database/tables).
+
+## Building
+
+Connect to your development SQLServer, windows users is most likely (localdb)\MSSQLLocalDb, and [run this script](create_quartz_tables.sql)
+
+Open the .sln, and run any one of the three projects. Done, you have a MT Scheduler!
\ No newline at end of file
diff --git a/create_quartz_tables.sql b/create_quartz_tables.sql
new file mode 100644
index 0000000..06a368e
--- /dev/null
+++ b/create_quartz_tables.sql
@@ -0,0 +1,367 @@
+-- this script is for SQL Server and Azure SQL
+
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1)
+ALTER TABLE [dbo].[QRTZ_TRIGGERS] DROP CONSTRAINT FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1)
+ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] DROP CONSTRAINT FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1)
+ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] DROP CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISFOREIGNKEY') = 1)
+ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] DROP CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS
+GO
+
+IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_QRTZ_JOB_LISTENERS_QRTZ_JOB_DETAILS]') AND parent_object_id = OBJECT_ID(N'[dbo].[QRTZ_JOB_LISTENERS]'))
+ALTER TABLE [dbo].[QRTZ_JOB_LISTENERS] DROP CONSTRAINT [FK_QRTZ_JOB_LISTENERS_QRTZ_JOB_DETAILS]
+
+IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_QRTZ_TRIGGER_LISTENERS_QRTZ_TRIGGERS]') AND parent_object_id = OBJECT_ID(N'[dbo].[QRTZ_TRIGGER_LISTENERS]'))
+ALTER TABLE [dbo].[QRTZ_TRIGGER_LISTENERS] DROP CONSTRAINT [FK_QRTZ_TRIGGER_LISTENERS_QRTZ_TRIGGERS]
+
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_CALENDARS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_CALENDARS]
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_CRON_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_CRON_TRIGGERS]
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_BLOB_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_BLOB_TRIGGERS]
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_FIRED_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_FIRED_TRIGGERS]
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_PAUSED_TRIGGER_GRPS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS]
+GO
+
+IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_JOB_LISTENERS]') AND type in (N'U'))
+DROP TABLE [dbo].[QRTZ_JOB_LISTENERS]
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_SCHEDULER_STATE]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_SCHEDULER_STATE]
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_LOCKS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_LOCKS]
+GO
+IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_TRIGGER_LISTENERS]') AND type in (N'U'))
+DROP TABLE [dbo].[QRTZ_TRIGGER_LISTENERS]
+
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_JOB_DETAILS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_JOB_DETAILS]
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_SIMPLE_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS]
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_SIMPROP_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].QRTZ_SIMPROP_TRIGGERS
+GO
+
+IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[QRTZ_TRIGGERS]') AND OBJECTPROPERTY(id, N'ISUSERTABLE') = 1)
+DROP TABLE [dbo].[QRTZ_TRIGGERS]
+GO
+
+CREATE TABLE [dbo].[QRTZ_CALENDARS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [CALENDAR_NAME] [NVARCHAR] (200) NOT NULL ,
+ [CALENDAR] [VARBINARY](MAX) NOT NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_CRON_TRIGGERS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [TRIGGER_NAME] [NVARCHAR] (150) NOT NULL ,
+ [TRIGGER_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [CRON_EXPRESSION] [NVARCHAR] (120) NOT NULL ,
+ [TIME_ZONE_ID] [NVARCHAR] (80)
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_FIRED_TRIGGERS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [ENTRY_ID] [NVARCHAR] (140) NOT NULL ,
+ [TRIGGER_NAME] [NVARCHAR] (150) NOT NULL ,
+ [TRIGGER_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [INSTANCE_NAME] [NVARCHAR] (200) NOT NULL ,
+ [FIRED_TIME] [BIGINT] NOT NULL ,
+ [SCHED_TIME] [BIGINT] NOT NULL ,
+ [PRIORITY] [INTEGER] NOT NULL ,
+ [STATE] [NVARCHAR] (16) NOT NULL,
+ [JOB_NAME] [NVARCHAR] (150) NULL ,
+ [JOB_GROUP] [NVARCHAR] (150) NULL ,
+ [IS_NONCONCURRENT] BIT NULL ,
+ [REQUESTS_RECOVERY] BIT NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [TRIGGER_GROUP] [NVARCHAR] (150) NOT NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_SCHEDULER_STATE] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [INSTANCE_NAME] [NVARCHAR] (200) NOT NULL ,
+ [LAST_CHECKIN_TIME] [BIGINT] NOT NULL ,
+ [CHECKIN_INTERVAL] [BIGINT] NOT NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_LOCKS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [LOCK_NAME] [NVARCHAR] (40) NOT NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_JOB_DETAILS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [JOB_NAME] [NVARCHAR] (150) NOT NULL ,
+ [JOB_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [DESCRIPTION] [NVARCHAR] (250) NULL ,
+ [JOB_CLASS_NAME] [NVARCHAR] (250) NOT NULL ,
+ [IS_DURABLE] BIT NOT NULL ,
+ [IS_NONCONCURRENT] BIT NOT NULL ,
+ [IS_UPDATE_DATA] BIT NOT NULL ,
+ [REQUESTS_RECOVERY] BIT NOT NULL ,
+ [JOB_DATA] [VARBINARY](MAX) NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [TRIGGER_NAME] [NVARCHAR] (150) NOT NULL ,
+ [TRIGGER_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [REPEAT_COUNT] [INTEGER] NOT NULL ,
+ [REPEAT_INTERVAL] [BIGINT] NOT NULL ,
+ [TIMES_TRIGGERED] [INTEGER] NOT NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [TRIGGER_NAME] [NVARCHAR] (150) NOT NULL ,
+ [TRIGGER_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [STR_PROP_1] [NVARCHAR] (512) NULL,
+ [STR_PROP_2] [NVARCHAR] (512) NULL,
+ [STR_PROP_3] [NVARCHAR] (512) NULL,
+ [INT_PROP_1] [INT] NULL,
+ [INT_PROP_2] [INT] NULL,
+ [LONG_PROP_1] [BIGINT] NULL,
+ [LONG_PROP_2] [BIGINT] NULL,
+ [DEC_PROP_1] [NUMERIC] (13,4) NULL,
+ [DEC_PROP_2] [NUMERIC] (13,4) NULL,
+ [BOOL_PROP_1] BIT NULL,
+ [BOOL_PROP_2] BIT NULL,
+ [TIME_ZONE_ID] [NVARCHAR] (80) NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_BLOB_TRIGGERS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [TRIGGER_NAME] [NVARCHAR] (150) NOT NULL ,
+ [TRIGGER_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [BLOB_DATA] [VARBINARY](MAX) NULL
+)
+GO
+
+CREATE TABLE [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME] [NVARCHAR] (120) NOT NULL ,
+ [TRIGGER_NAME] [NVARCHAR] (150) NOT NULL ,
+ [TRIGGER_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [JOB_NAME] [NVARCHAR] (150) NOT NULL ,
+ [JOB_GROUP] [NVARCHAR] (150) NOT NULL ,
+ [DESCRIPTION] [NVARCHAR] (250) NULL ,
+ [NEXT_FIRE_TIME] [BIGINT] NULL ,
+ [PREV_FIRE_TIME] [BIGINT] NULL ,
+ [PRIORITY] [INTEGER] NULL ,
+ [TRIGGER_STATE] [NVARCHAR] (16) NOT NULL ,
+ [TRIGGER_TYPE] [NVARCHAR] (8) NOT NULL ,
+ [START_TIME] [BIGINT] NOT NULL ,
+ [END_TIME] [BIGINT] NULL ,
+ [CALENDAR_NAME] [NVARCHAR] (200) NULL ,
+ [MISFIRE_INSTR] [INTEGER] NULL ,
+ [JOB_DATA] [VARBINARY](MAX) NULL
+)
+GO
+
+ALTER TABLE [dbo].[QRTZ_CALENDARS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_CALENDARS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [CALENDAR_NAME]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_CRON_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_FIRED_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_FIRED_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [ENTRY_ID]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_PAUSED_TRIGGER_GRPS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_GROUP]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_SCHEDULER_STATE] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_SCHEDULER_STATE] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [INSTANCE_NAME]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_LOCKS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_LOCKS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [LOCK_NAME]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_JOB_DETAILS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_JOB_DETAILS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [JOB_NAME],
+ [JOB_GROUP]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_SIMPLE_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_SIMPROP_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_TRIGGERS] WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ )
+GO
+
+ALTER TABLE [dbo].QRTZ_BLOB_TRIGGERS WITH NOCHECK ADD
+ CONSTRAINT [PK_QRTZ_BLOB_TRIGGERS] PRIMARY KEY CLUSTERED
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ )
+GO
+
+ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) REFERENCES [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) ON DELETE CASCADE
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) REFERENCES [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) ON DELETE CASCADE
+GO
+
+ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) REFERENCES [dbo].[QRTZ_TRIGGERS] (
+ [SCHED_NAME],
+ [TRIGGER_NAME],
+ [TRIGGER_GROUP]
+ ) ON DELETE CASCADE
+GO
+
+ALTER TABLE [dbo].[QRTZ_TRIGGERS] ADD
+ CONSTRAINT [FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS] FOREIGN KEY
+ (
+ [SCHED_NAME],
+ [JOB_NAME],
+ [JOB_GROUP]
+ ) REFERENCES [dbo].[QRTZ_JOB_DETAILS] (
+ [SCHED_NAME],
+ [JOB_NAME],
+ [JOB_GROUP]
+ )
+GO
+
+CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP)
+CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP)
+CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME)
+CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP)
+CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE)
+CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE)
+CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE)
+CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME)
+CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME)
+CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME)
+CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE)
+CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE)
+
+CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME)
+CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY)
+CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP)
+CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP)
+CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP)
+GO
\ No newline at end of file
diff --git a/src/Net461TopShelf/App.config b/src/Net461TopShelf/App.config
new file mode 100644
index 0000000..4f401e3
--- /dev/null
+++ b/src/Net461TopShelf/App.config
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Net461TopShelf/Net461TopShelf.csproj b/src/Net461TopShelf/Net461TopShelf.csproj
new file mode 100644
index 0000000..023c9a5
--- /dev/null
+++ b/src/Net461TopShelf/Net461TopShelf.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net461
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Net461TopShelf/Program.cs b/src/Net461TopShelf/Program.cs
new file mode 100644
index 0000000..cc4fca9
--- /dev/null
+++ b/src/Net461TopShelf/Program.cs
@@ -0,0 +1,113 @@
+using Autofac;
+using GreenPipes;
+using MassTransit;
+using MassTransit.QuartzIntegration;
+using MassTransit.Scheduling;
+using MassTransit.SerilogIntegration;
+using Quartz;
+using Quartz.Impl;
+using Serilog;
+using System;
+using System.Configuration;
+using Topshelf;
+using Topshelf.HostConfigurators;
+using Topshelf.Logging;
+
+namespace Net461TopShelf
+{
+ class Program
+ {
+ static int Main()
+ {
+ ConfigureSerilog();
+
+ try
+ {
+ return (int)HostFactory.Run(Configure);
+ }
+ catch (Exception e)
+ {
+ Log.Logger.Fatal(e.ToString());
+ throw;
+ }
+ }
+
+ static void Configure(HostConfigurator hostConfigurator)
+ {
+ hostConfigurator.Service(s =>
+ {
+ // TopShelf will create a new Service every time you Stop/Start within Windows Services.
+ s.ConstructUsing(() =>
+ {
+ var builder = new ContainerBuilder();
+
+ // Service Bus
+ builder.AddMassTransit(cfg =>
+ {
+ cfg.AddBus(ConfigureBus);
+ });
+
+ // Should Only ever register one Service
+ builder.RegisterType()
+ .SingleInstance();
+
+ builder.Register(x => new StdSchedulerFactory().GetScheduler().ConfigureAwait(false).GetAwaiter().GetResult())
+ .SingleInstance();
+
+ var container = builder.Build();
+
+ return container.Resolve();
+ });
+
+ s.WhenStarted((service, control) => service.Start());
+ s.WhenStopped((service, control) => service.Stop());
+ });
+
+ hostConfigurator.SetDisplayName("MT.Net461.Scheduler");
+ hostConfigurator.SetServiceName("MT.Net461.Scheduler");
+ hostConfigurator.SetDescription("MT.Net461.Scheduler");
+ }
+
+ static IBusControl ConfigureBus(IComponentContext context)
+ {
+ var scheduler = context.Resolve();
+
+ return Bus.Factory.CreateUsingRabbitMq(cfg =>
+ {
+ var host = cfg.Host(ConfigurationManager.AppSettings["RabbitMQHost"], ConfigurationManager.AppSettings["RabbitMQVirtualHost"], h =>
+ {
+ h.Username(ConfigurationManager.AppSettings["RabbitMQUsername"]);
+ h.Password(ConfigurationManager.AppSettings["RabbitMQPassword"]);
+ });
+
+ cfg.UseJsonSerializer(); // Because we are using json within Quartz for serializer type
+
+ cfg.ReceiveEndpoint(host, ConfigurationManager.AppSettings["QueueName"], endpoint =>
+ {
+ var partitionCount = Environment.ProcessorCount;
+ endpoint.PrefetchCount = (ushort)(partitionCount);
+ var partitioner = endpoint.CreatePartitioner(partitionCount);
+
+ endpoint.Consumer(() => new ScheduleMessageConsumer(scheduler), x =>
+ x.Message(m => m.UsePartitioner(partitioner, p => p.Message.CorrelationId)));
+ endpoint.Consumer(() => new CancelScheduledMessageConsumer(scheduler),
+ x => x.Message(m => m.UsePartitioner(partitioner, p => p.Message.TokenId)));
+ });
+ });
+ }
+
+ static void ConfigureSerilog()
+ {
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .WriteTo.Console()
+ .CreateLogger();
+
+ // Configure Topshelf Logger
+ SerilogLogWriterFactory.Use(Log.Logger);
+
+ // MassTransit to use Serilog
+ SerilogLogger.Use(Log.Logger);
+ }
+ }
+}
diff --git a/src/Net461TopShelf/SchedulerService.cs b/src/Net461TopShelf/SchedulerService.cs
new file mode 100644
index 0000000..217cd44
--- /dev/null
+++ b/src/Net461TopShelf/SchedulerService.cs
@@ -0,0 +1,55 @@
+using MassTransit;
+using MassTransit.QuartzIntegration;
+using MassTransit.Util;
+using Quartz;
+using Quartz.Impl;
+using System;
+
+namespace Net461TopShelf
+{
+ public class SchedulerService
+ {
+ IScheduler _scheduler;
+ IBusControl _busControl;
+ BusHandle _busHandle;
+
+ public SchedulerService(IBusControl busControl, IScheduler scheduler)
+ {
+ _busControl = busControl;
+ _scheduler = scheduler;
+ }
+
+ public bool Start()
+ {
+ try
+ {
+ _busHandle = TaskUtil.Await(() => _busControl.StartAsync());
+
+ _scheduler.JobFactory = new MassTransitJobFactory(_busControl);
+
+ TaskUtil.Await(() => _scheduler.Start());
+ }
+ catch (Exception)
+ {
+ TaskUtil.Await(() => _scheduler.Shutdown());
+ throw;
+ }
+
+ Console.WriteLine("Started");
+ return true;
+ }
+
+ public bool Stop()
+ {
+ TaskUtil.Await(() => _scheduler.Standby());
+
+ if (_busHandle != null)
+ TaskUtil.Await(() => _busHandle.StopAsync());
+
+ TaskUtil.Await(() => _scheduler.Shutdown());
+
+ Console.WriteLine("Stopped");
+ return true;
+ }
+ }
+}
diff --git a/src/NetCore/AppConfig.cs b/src/NetCore/AppConfig.cs
new file mode 100644
index 0000000..2f755f6
--- /dev/null
+++ b/src/NetCore/AppConfig.cs
@@ -0,0 +1,11 @@
+namespace NetCore
+{
+ public class AppConfig
+ {
+ public string Host { get; set; }
+ public string VirtualHost { get; set; }
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public string QueueName { get; set; }
+ }
+}
diff --git a/src/NetCore/MassTransitConsoleHostedService.cs b/src/NetCore/MassTransitConsoleHostedService.cs
new file mode 100644
index 0000000..1a81339
--- /dev/null
+++ b/src/NetCore/MassTransitConsoleHostedService.cs
@@ -0,0 +1,63 @@
+using MassTransit;
+using MassTransit.Logging.Tracing;
+using MassTransit.QuartzIntegration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Quartz;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NetCore
+{
+ public class MassTransitConsoleHostedService : IHostedService
+ {
+ readonly IBusControl _bus;
+ readonly ILogger _logger;
+ IScheduler _scheduler;
+
+ public MassTransitConsoleHostedService(IBusControl bus, ILoggerFactory loggerFactory, IScheduler scheduler)
+ {
+ _bus = bus;
+
+ if (loggerFactory != null && MassTransit.Logging.Logger.Current.GetType() == typeof(TraceLogger))
+ MassTransit.ExtensionsLoggingIntegration.ExtensionsLogger.Use(loggerFactory);
+ _logger = loggerFactory.CreateLogger();
+
+ _scheduler = scheduler;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("Starting bus");
+ await _bus.StartAsync(cancellationToken).ConfigureAwait(false);
+
+ _scheduler.JobFactory = new MassTransitJobFactory(_bus);
+ try
+ {
+ _logger.LogInformation("Starting scheduler");
+ await _scheduler.Start();
+ }
+ catch (Exception)
+ {
+ await _scheduler.Shutdown();
+
+ throw;
+ }
+
+ _logger.LogInformation("Started");
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ await _scheduler.Standby();
+
+ _logger.LogInformation("Stopping");
+ await _bus.StopAsync(cancellationToken);
+
+ await _scheduler.Shutdown();
+
+ _logger.LogInformation("Stopped");
+ }
+ }
+}
diff --git a/src/NetCore/NetCore.csproj b/src/NetCore/NetCore.csproj
new file mode 100644
index 0000000..49a1c09
--- /dev/null
+++ b/src/NetCore/NetCore.csproj
@@ -0,0 +1,34 @@
+
+
+
+ Exe
+ netcoreapp2.2
+ latest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/src/NetCore/Program.cs b/src/NetCore/Program.cs
new file mode 100644
index 0000000..fe05352
--- /dev/null
+++ b/src/NetCore/Program.cs
@@ -0,0 +1,87 @@
+using GreenPipes;
+using MassTransit;
+using MassTransit.QuartzIntegration;
+using MassTransit.Scheduling;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Quartz;
+using Quartz.Impl;
+using System;
+using System.Threading.Tasks;
+
+namespace NetCore
+{
+ class Program
+ {
+ static async Task Main(string[] args)
+ {
+ var builder = CreateHostBuilder(args);
+
+ await builder.RunConsoleAsync();
+ }
+
+ static IHostBuilder CreateHostBuilder(string[] args) =>
+ new HostBuilder()
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.AddJsonFile("appsettings.json", optional: true);
+ config.AddEnvironmentVariables();
+
+ if (args != null)
+ config.AddCommandLine(args);
+ })
+ .ConfigureServices((hostContext, services) =>
+ {
+ services.Configure(hostContext.Configuration.GetSection("AppConfig"));
+
+ // Service Bus
+ services.AddMassTransit(cfg =>
+ {
+ cfg.AddBus(ConfigureBus);
+ });
+
+ services.AddHostedService();
+
+ services.AddSingleton(x => new StdSchedulerFactory().GetScheduler().ConfigureAwait(false).GetAwaiter().GetResult());
+ })
+ .ConfigureLogging((hostingContext, logging) =>
+ {
+ logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
+ logging.AddConsole();
+ });
+
+
+
+ static IBusControl ConfigureBus(IServiceProvider provider)
+ {
+ var options = provider.GetRequiredService>().Value;
+ var scheduler = provider.GetRequiredService();
+
+ return Bus.Factory.CreateUsingRabbitMq(cfg =>
+ {
+ var host = cfg.Host(options.Host, options.VirtualHost, h =>
+ {
+ h.Username(options.Username);
+ h.Password(options.Password);
+ });
+
+ cfg.UseJsonSerializer(); // Because we are using json within Quartz for serializer type
+
+ cfg.ReceiveEndpoint(host, options.QueueName, endpoint =>
+ {
+ var partitionCount = Environment.ProcessorCount;
+ endpoint.PrefetchCount = (ushort)(partitionCount);
+ var partitioner = endpoint.CreatePartitioner(partitionCount);
+
+ endpoint.Consumer(() => new ScheduleMessageConsumer(scheduler), x =>
+ x.Message(m => m.UsePartitioner(partitioner, p => p.Message.CorrelationId)));
+ endpoint.Consumer(() => new CancelScheduledMessageConsumer(scheduler),
+ x => x.Message(m => m.UsePartitioner(partitioner, p => p.Message.TokenId)));
+ });
+ });
+ }
+ }
+}
diff --git a/src/NetCore/appsettings.json b/src/NetCore/appsettings.json
new file mode 100644
index 0000000..ffd775c
--- /dev/null
+++ b/src/NetCore/appsettings.json
@@ -0,0 +1,14 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug"
+ }
+ },
+ "AppConfig": {
+ "Host": "localhost",
+ "VirtualHost": "/",
+ "Username": "guest",
+ "Password": "guest",
+ "QueueName": "quartz-scheduler"
+ }
+}
\ No newline at end of file
diff --git a/src/NetCore/quartz.config b/src/NetCore/quartz.config
new file mode 100644
index 0000000..cab3710
--- /dev/null
+++ b/src/NetCore/quartz.config
@@ -0,0 +1,13 @@
+quartz.scheduler.instanceName = MassTransit-Scheduler
+quartz.scheduler.instanceId = AUTO
+quartz.serializer.type = json
+quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
+quartz.threadPool.threadCount = 10
+quartz.jobStore.misfireThreshold = 60000
+quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
+quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz
+quartz.jobStore.tablePrefix = QRTZ_
+quartz.jobStore.dataSource = quartzDS
+quartz.dataSource.quartzDS.connectionString = Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=mt-scheduler;Trusted_Connection=True;MultipleActiveResultSets=True;Connection Timeout=30
+quartz.dataSource.quartzDS.provider = SqlServer
+quartz.jobStore.useProperties = true
\ No newline at end of file
diff --git a/src/NetCoreWinSvc/AppConfig.cs b/src/NetCoreWinSvc/AppConfig.cs
new file mode 100644
index 0000000..6fcc840
--- /dev/null
+++ b/src/NetCoreWinSvc/AppConfig.cs
@@ -0,0 +1,11 @@
+namespace NetCoreWinSvc
+{
+ public class AppConfig
+ {
+ public string Host { get; set; }
+ public string VirtualHost { get; set; }
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public string QueueName { get; set; }
+ }
+}
diff --git a/src/NetCoreWinSvc/MassTransitConsoleHostedService.cs b/src/NetCoreWinSvc/MassTransitConsoleHostedService.cs
new file mode 100644
index 0000000..466d02f
--- /dev/null
+++ b/src/NetCoreWinSvc/MassTransitConsoleHostedService.cs
@@ -0,0 +1,63 @@
+using MassTransit;
+using MassTransit.Logging.Tracing;
+using MassTransit.QuartzIntegration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Quartz;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NetCoreWinSvc
+{
+ public class MassTransitConsoleHostedService : IHostedService
+ {
+ readonly IBusControl _bus;
+ readonly ILogger _logger;
+ IScheduler _scheduler;
+
+ public MassTransitConsoleHostedService(IBusControl bus, ILoggerFactory loggerFactory, IScheduler scheduler)
+ {
+ _bus = bus;
+
+ if (loggerFactory != null && MassTransit.Logging.Logger.Current.GetType() == typeof(TraceLogger))
+ MassTransit.ExtensionsLoggingIntegration.ExtensionsLogger.Use(loggerFactory);
+ _logger = loggerFactory.CreateLogger();
+
+ _scheduler = scheduler;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("Starting bus");
+ await _bus.StartAsync(cancellationToken).ConfigureAwait(false);
+
+ _scheduler.JobFactory = new MassTransitJobFactory(_bus);
+ try
+ {
+ _logger.LogInformation("Starting scheduler");
+ await _scheduler.Start();
+ }
+ catch(Exception)
+ {
+ await _scheduler.Shutdown();
+
+ throw;
+ }
+
+ _logger.LogInformation("Started");
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ await _scheduler.Standby();
+
+ _logger.LogInformation("Stopping");
+ await _bus.StopAsync(cancellationToken);
+
+ await _scheduler.Shutdown();
+
+ _logger.LogInformation("Stopped");
+ }
+ }
+}
diff --git a/src/NetCoreWinSvc/NetCoreWinSvc.csproj b/src/NetCoreWinSvc/NetCoreWinSvc.csproj
new file mode 100644
index 0000000..9f5f607
--- /dev/null
+++ b/src/NetCoreWinSvc/NetCoreWinSvc.csproj
@@ -0,0 +1,35 @@
+
+
+
+ Exe
+ netcoreapp2.2
+ latest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/src/NetCoreWinSvc/Program.cs b/src/NetCoreWinSvc/Program.cs
new file mode 100644
index 0000000..e2f0d56
--- /dev/null
+++ b/src/NetCoreWinSvc/Program.cs
@@ -0,0 +1,98 @@
+using GreenPipes;
+using MassTransit;
+using MassTransit.QuartzIntegration;
+using MassTransit.Scheduling;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Quartz;
+using Quartz.Impl;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace NetCoreWinSvc
+{
+ class Program
+ {
+ static async Task Main(string[] args)
+ {
+ var isService = !(Debugger.IsAttached || args.Contains("--console"));
+
+ var builder = CreateHostBuilder(args);
+
+ if (isService)
+ {
+ await builder.RunAsServiceAsync();
+ }
+ else
+ {
+ await builder.RunConsoleAsync();
+ }
+ }
+
+ static IHostBuilder CreateHostBuilder(string[] args) =>
+ new HostBuilder()
+ .ConfigureAppConfiguration((hostingContext, config) =>
+ {
+ config.AddJsonFile("appsettings.json", optional: true);
+ config.AddEnvironmentVariables();
+
+ if (args != null)
+ config.AddCommandLine(args);
+ })
+ .ConfigureServices((hostContext, services) =>
+ {
+ services.Configure(hostContext.Configuration.GetSection("AppConfig"));
+
+ // Service Bus
+ services.AddMassTransit(cfg =>
+ {
+ cfg.AddBus(ConfigureBus);
+ });
+
+ services.AddHostedService();
+
+ services.AddSingleton(x => new StdSchedulerFactory().GetScheduler().ConfigureAwait(false).GetAwaiter().GetResult());
+ })
+ .ConfigureLogging((hostingContext, logging) =>
+ {
+ logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
+ logging.AddConsole();
+ });
+
+
+
+ static IBusControl ConfigureBus(IServiceProvider provider)
+ {
+ var options = provider.GetRequiredService>().Value;
+ var scheduler = provider.GetRequiredService();
+
+ return Bus.Factory.CreateUsingRabbitMq(cfg =>
+ {
+ var host = cfg.Host(options.Host, options.VirtualHost, h =>
+ {
+ h.Username(options.Username);
+ h.Password(options.Password);
+ });
+
+ cfg.UseJsonSerializer(); // Because we are using json within Quartz for serializer type
+
+ cfg.ReceiveEndpoint(host, options.QueueName, endpoint =>
+ {
+ var partitionCount = Environment.ProcessorCount;
+ endpoint.PrefetchCount = (ushort)(partitionCount);
+ var partitioner = endpoint.CreatePartitioner(partitionCount);
+
+ endpoint.Consumer(() => new ScheduleMessageConsumer(scheduler), x =>
+ x.Message(m => m.UsePartitioner(partitioner, p => p.Message.CorrelationId)));
+ endpoint.Consumer(() => new CancelScheduledMessageConsumer(scheduler),
+ x => x.Message(m => m.UsePartitioner(partitioner, p => p.Message.TokenId)));
+ });
+ });
+ }
+ }
+}
diff --git a/src/NetCoreWinSvc/ServiceBaseLifetime.cs b/src/NetCoreWinSvc/ServiceBaseLifetime.cs
new file mode 100644
index 0000000..c9374d1
--- /dev/null
+++ b/src/NetCoreWinSvc/ServiceBaseLifetime.cs
@@ -0,0 +1,63 @@
+using Microsoft.Extensions.Hosting;
+using System;
+using System.ServiceProcess;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NetCoreWinSvc
+{
+ public class ServiceBaseLifetime : ServiceBase, IHostLifetime
+ {
+ private readonly TaskCompletionSource