diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/Startup.cs b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/Startup.cs index be4d819..2c6b66d 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/Startup.cs +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/Startup.cs @@ -1,18 +1,13 @@ +using Quartz.Plugins.RecentHistory; +using Quartz.Plugins.RecentHistory.Impl; using TransactionProcessing.SchedulerService.Jobs.Jobs; namespace TransactionProcessing.SchedulerService { - using System; - using System.Collections.Generic; - using System.Configuration; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Threading; - using System.Threading.Tasks; using Jobs; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; + using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -20,8 +15,21 @@ namespace TransactionProcessing.SchedulerService using NLog.Extensions.Logging; using Quartz; using Quartz.Impl; + using Quartz.Impl.AdoJobStore.Common; + using Quartz.Impl.Matchers; + using Quartz.Spi; using Shared.Logger; using SilkierQuartz; + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Data; + using System.Data.Common; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; using ILogger = Microsoft.Extensions.Logging.ILogger; using SetupBuilderExtensions = NLog.SetupBuilderExtensions; @@ -42,9 +50,6 @@ public Startup(IWebHostEnvironment webHostEnvironment){ Startup.Configuration = builder.Build(); Startup.WebHostEnvironment = webHostEnvironment; - - String connectionString = Startup.Configuration.GetConnectionString("SchedulerReadModel"); - Startup.AddOrUpdateConnectionString("SchedulerReadModel", connectionString); } #endregion @@ -59,27 +64,6 @@ public Startup(IWebHostEnvironment webHostEnvironment){ #region Methods - public static void AddOrUpdateConnectionString(String name, - String connectionString){ - try{ - Configuration configFile = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); - ConnectionStringSettingsCollection settings = configFile.ConnectionStrings.ConnectionStrings; - - if (settings[name] == null){ - settings.Add(new ConnectionStringSettings(name, connectionString)); - } - else{ - settings[name].ConnectionString = connectionString; - } - - configFile.Save(ConfigurationSaveMode.Modified); - System.Configuration.ConfigurationManager.RefreshSection(configFile.AppSettings.SectionInformation.Name); - } - catch(ConfigurationErrorsException){ - Console.WriteLine("Error writing connection string"); - } - } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) { @@ -110,12 +94,21 @@ public void Configure(IApplicationBuilder app, app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); } + + public void ConfigureServices(IServiceCollection services){ services.AddRazorPages(); this.RegisterJobs(services); - IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; + services.AddScheduler(Configuration); + + var serviceProvider = services.BuildServiceProvider(); + ISchedulerFactory schedulerFactory = serviceProvider.GetRequiredService(); + + IScheduler scheduler = schedulerFactory.GetScheduler().Result; + IExecutionHistoryStore store = new InProcExecutionHistoryStore(); + scheduler.Context.SetExecutionHistoryStore(store); services.AddSilkierQuartz(s => { s.VirtualPathRoot = "/quartz"; @@ -123,15 +116,16 @@ public void ConfigureServices(IServiceCollection services){ s.DefaultDateFormat = "yyyy-MM-dd"; s.DefaultTimeFormat = "HH:mm:ss"; s.Scheduler = scheduler; - }, + + }, a => { a.AccessRequirement = SilkierQuartzAuthenticationOptions.SimpleAccessRequirement.AllowAnonymous; }); } - + private void RegisterJobs(IServiceCollection services) { Type type = typeof(BaseJob); IEnumerable jobs = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()) - .Where(p => type.IsAssignableFrom(p) + .Where(p => type.IsAssignableFrom(p) && p.IsInterface == false && p.IsAbstract == false); @@ -143,4 +137,121 @@ private void RegisterJobs(IServiceCollection services) #endregion } + + public class CustomSqlServerConnectionProvider : IDbProvider + { + private readonly ILogger logger; + private readonly IConfiguration configuration; + + public CustomSqlServerConnectionProvider( + ILogger logger, + IConfiguration configuration) + { + this.logger = logger; + this.configuration = configuration; + Metadata = new DbMetadata + { + AssemblyName = typeof(SqlConnection).AssemblyQualifiedName, + BindByName = true, + CommandType = typeof(SqlCommand), + ConnectionType = typeof(SqlConnection), + DbBinaryTypeName = "VarBinary", + ExceptionType = typeof(SqlException), + ParameterDbType = typeof(SqlDbType), + ParameterDbTypePropertyName = "SqlDbType", + ParameterNamePrefix = "@", + ParameterType = typeof(SqlParameter), + UseParameterNamePrefixInParameterCollection = true + }; + Metadata.Init(); + } + + public void Initialize() + { + logger.LogInformation("Initializing"); + } + + public DbCommand CreateCommand() + { + return new SqlCommand(); + } + + public DbConnection CreateConnection() + { + return new SqlConnection(ConnectionString); + } + + public string ConnectionString + { + get => configuration.GetConnectionString("Quartz")!; + set => throw new NotImplementedException(); + } + + public DbMetadata Metadata { get; } + + public void Shutdown() + { + logger.LogInformation("Shutting down"); + } + } + + public static class Extensions { + public static IServiceCollection AddScheduler(this IServiceCollection services, IConfiguration configuration) + { + // base configuration for DI, read from appSettings.json + services.Configure(configuration.GetSection("Quartz")); + + // if you are using persistent job store, you might want to alter some options + services.Configure(options => + { + options.Scheduling.IgnoreDuplicates = true; // default: false + options.Scheduling.OverWriteExistingData = true; // default: true + }); + + // custom connection provider + services.AddSingleton(); + + services.AddQuartz(q => { + // handy when part of cluster or you want to otherwise identify multiple schedulers + q.SchedulerId = "Scheduler-Core"; + + // you can control whether job interruption happens for running jobs when scheduler is shutting down + q.InterruptJobsOnShutdown = true; + + // when QuartzHostedServiceOptions.WaitForJobsToComplete = true or scheduler.Shutdown(waitForJobsToComplete: true) + q.InterruptJobsOnShutdownWithWait = true; + + // we can change from the default of 1 + q.MaxBatchSize = 5; + + // we take this from appsettings.json, just show it's possible + q.SchedulerName = "Txn Processing Scheduler"; + + // these are the defaults + q.UseSimpleTypeLoader(); + q.UsePersistentStore(s => { + s.PerformSchemaValidation = false; // default + //s.UseProperties = true; // preferred, but not default + s.RetryInterval = TimeSpan.FromSeconds(15); + s.UseSqlServer(sqlServer => + { + sqlServer.ConnectionString = configuration.GetConnectionString("Quartz"); + + // this is the default + sqlServer.TablePrefix = "QRTZ_"; + }); + s.UseNewtonsoftJsonSerializer(); + }); + q.UseDefaultThreadPool(maxConcurrency: 10); + q.UseTimeZoneConverter(); + + ExecutionHistoryPlugin p = new ExecutionHistoryPlugin(); + p.StoreType = Type.GetType("Quartz.Plugins.RecentHistory.Impl.InProcExecutionHistoryStore,Quartz.Plugins.RecentHistory"); + p.Name = "ExecutionHistoryPlugin"; + q.AddJobListener(p); + }); + + return services; + } + } } \ No newline at end of file diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.csproj b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.csproj index b1877d5..87ce2a8 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.csproj +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.csproj @@ -22,9 +22,6 @@ - - Always - Always diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config index 08f94bf..82d791e 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService.dll.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json index 2de974a..64ab144 100644 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json +++ b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/appsettings.json @@ -1,5 +1,20 @@ { "ConnectionStrings": { - "SchedulerReadModel": "server=127.0.0.1;user id=sa;password=Sc0tland;database=Scheduler;Encrypt=True;TrustServerCertificate=True" - } + "Quartz": "server=127.0.0.1;user id=sa;password=Sc0tland;database=Scheduler;Encrypt=True;TrustServerCertificate=True" + }, + //"Quartz": { + // "quartz.scheduler.instanceName": "Txn Processing Scheduler", + // "quartz.dataSource.JobStore.provider": "SqlServer", + // //"quartz.dataSource.JobStore.connectionStringName": "X", + // "quartz.jobStore.useProperties" :true, + // "quartz.jobStore.tablePrefix": "QRTZ_", + // //"quartz.jobStore.type": "Quartz.Impl.AdoJobStore.JobStoreTX,Quartz", + // "quartz.jobStore.performSchemaValidation":false, + // "quartz.serializer.type": "json", + // //"quartz.jobStore.dataSource": "JobStore", + // //"quartz.jobStore.driverDelegateType": "Quartz.Impl.AdoJobStore.SqlServerDelegate,Quartz", + // "quartz.plugin.recentHistory.type": "Quartz.Plugins.RecentHistory.ExecutionHistoryPlugin,Quartz.Plugins.RecentHistory", + // "quartz.plugin.recentHistory.storeType": "Quartz.Plugins.RecentHistory.Impl.InProcExecutionHistoryStore,Quartz.Plugins.RecentHistory", + // "quartz.plugin.timeZoneConverter.type": "Quartz.Plugin.TimeZoneConverter.TimeZoneConverterPlugin,Quartz.Plugins.TimeZoneConverter" + //} } diff --git a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/quartz.config b/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/quartz.config deleted file mode 100644 index fea4ad1..0000000 --- a/TransactionProcessing.SchedulerService/TransactionProcessing.SchedulerService/quartz.config +++ /dev/null @@ -1,13 +0,0 @@ -quartz.scheduler.instanceName = Txn Processing Scheduler -quartz.dataSource.JobStore.provider = SqlServer -quartz.dataSource.JobStore.connectionStringName = SchedulerReadModel -quartz.jobStore.useProperties = true -quartz.jobStore.tablePrefix = QRTZ_ -quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz -quartz.jobStore.performSchemaValidation = false -quartz.serializer.type = json -quartz.jobStore.dataSource = JobStore -quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz -quartz.plugin.recentHistory.type = Quartz.Plugins.RecentHistory.ExecutionHistoryPlugin, Quartz.Plugins.RecentHistory -quartz.plugin.recentHistory.storeType = Quartz.Plugins.RecentHistory.Impl.InProcExecutionHistoryStore, Quartz.Plugins.RecentHistory -quartz.plugin.timeZoneConverter.type = Quartz.Plugin.TimeZoneConverter.TimeZoneConverterPlugin, Quartz.Plugins.TimeZoneConverter \ No newline at end of file