I am using an ASP.Net website as a sort of back-end control panel, and I want to set it up so that when the user adds something to my database, it will create/schedule a job to send out a reminder to everybody on some date.
I have the job and trigger parts down and working, however I want to set it up to use AdoJobStore so that these jobs won't be lost (in case there is an instance where the reminder doesn't have to be sent out for a whole month or two).
I've tried using their official tutorial, some relevant posts here, and other guides I've found through google, but I can't figure out how to set this up at all. Most provide code that needs to be added to some kind of configuration file, but I can't seem to find any - I have seen them saying to edit quartz.config, web.config, or quartz.properties. I can only find the web.config in ASP.net my project, but I can't seem to get any of the examples working inside this file.
You need to define an ADOJobStore in your app.config/web.config and then some quartz.config. I call mine QuartzDataStoreSettingsDatabase.config
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<quartz configSource="QuartzDataStoreSettingsDatabase.config" />
Then the file for quartz specific stuff
QuartzDataStoreSettingsDatabase.config
<quartz>
<add key="quartz.scheduler.instanceName" value="ExampleDefaultQuartzSchedulerFromConfigFileSqlServer"/>
<add key="quartz.scheduler.instanceId" value="instance_one"/>
<add key="quartz.threadPool.threadCount" value="10"/>
<add key="quartz.threadPool.threadPriority" value="Normal"/>
<!--
org.quartz.scheduler.idleWaitTime
Is the amount of time in milliseconds that the scheduler will wait before re-queries for available triggers when the scheduler is otherwise idle. Normally you should not have to 'tune' this parameter, unless you're using XA transactions, and are having problems with delayed firings of triggers that should fire immediately.
It defaults to every 30 seconds until it finds a trigger. Once it finds any triggers, it gets the time of the next trigger to fire and stops checking until then, unless a trigger changes. -->
<add key="quartz.scheduler.idleWaitTime" value ="5000"/>
<!-- Misfire : see http://nurkiewicz.blogspot.com/2012/04/quartz-scheduler-misfire-instructions.html -->
<add key="quartz.jobStore.misfireThreshold" value="60000"/>
<add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"/>
<add key="quartz.jobStore.tablePrefix" value="QRTZ_"/>
<add key="quartz.jobStore.clustered" value="false"/>
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz"/>
<add key="quartz.jobStore.dataSource" value="MySqlServerFullVersion"/>
<add key="quartz.jobStore.useProperties" value="false"/>
<add key="quartz.dataSource.MySqlServerFullVersion.connectionString" value="SuperSecret!!"/>
<add key="quartz.dataSource.MySqlServerFullVersion.provider" value="SqlServer-20"/>
</quartz>
See my answer here:
Unable to save anything to the Quartz.net ado store
Then when an "event" happens in your website world.......you need to schedule a job programatically.
NameValueCollection config = (NameValueCollection)ConfigurationManager.GetSection("quartz");
ShowConfiguration(config, logger);
ISchedulerFactory factory = new StdSchedulerFactory(config);
IScheduler sched = factory.GetScheduler();
/* the below code has to be tweaked for YOUR Job */
IJobDetail textFilePoppingJobJobDetail = JobBuilder.Create<TextFilePoppingNonConcurrentJob>()
.WithIdentity("textFilePoppingJobJobDetail001", "groupName007")
.UsingJobData("JobDetailParameter001", "Abcd1234")
.Build();
ITrigger textFilePoppingJobJobDetailTrigger001 = TriggerBuilder.Create()
.WithIdentity("textFilePoppingJobJobDetailTrigger001", "groupName007")
.UsingJobData("TriggerParameter001", "Bcde2345")
.UsingJobData("TempDirectorySubFolderName", "MyTempDirectorySubFolderName")
.UsingJobData("DestinationFullFolderName", #"C:\SomeFolder")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever()
/* .WithRepeatCount(1) */
)
.Build();
sched.ScheduleJob(textFilePoppingJobJobDetail, textFilePoppingJobJobDetailTrigger001);
Related
I use Serilog for logging. I need to configure App.config as to remove 2 standard columns from Logs table (StandardColumn.Properties and StandardColumn.MessageTemplate). I've searched for it in Serilog docs and other resources but can't find how to do it. This is what I got so far:
<configuration>
<appSettings>
<add key="serilog:using:MSSqlServer" value="Serilog.Sinks.MSSqlServer" />
<add key="serilog:write-to:MSSqlServer.connectionString" value="Data Source=SomeServer;initial catalog=DB; integrated security=True" />
<add key="serilog:write-to:MSSqlServer.tableName" value="Logs" />
<add key="serilog:minimum-level" value="Debug" />
</appSettings>
</configuration>
I guess I would have to add this line:
<add key="serilog:write-to:MSSqlServer.columnOptions" value="someColOptions" />
but how should I define someColOptions? For example, I removed columns using this kind of code:
var colOptions = new Serilog.Sinks.MSSqlServer.ColumnOptions();
colOptions.Store.Remove(StandardColumn.Properties);
colOptions.Store.Remove(StandardColumn.MessageTemplate);
Log.Logger = new LoggerConfiguration()
.WriteTo
.MSSqlServer(
connectionString: conn_string,
sinkOptions: new MSSqlServerSinkOptions { TableName = "Logs" },
columnOptions: colOptions)
.MinimumLevel.Debug()
.CreateLogger();
and now I want to do the same in App.config
I had a similar question, I've been able to find out that by adding the following, the configSections has to be first section after
that this will:
create a Logs table in the specified Database (from the connection string)
Use the Serilog database account (from the connection string)
Remove the column called Properties from the Logs table
Add a new column called RunId that declared as an int
<configuration>
<configSections>
<section name="MSSqlServerSettingsSection" type="Serilog.Configuration.MSSqlServerConfigurationSection, Serilog.Sinks.MSSqlServer"/>
</configSections>
<MSSqlServerSettingsSection DisableTriggers="false" ClusteredColumnstoreIndex="false" PrimaryKeyColumnName="Id">
<!-- SinkOptions parameters -->
<TableName Value="Logs"/>
<BatchPeriod Value="00:00:15"/>
<RemoveStandardColumns>
<remove Name="Properties"/>
</RemoveStandardColumns>
<Columns>
<add ColumnName="RunId" DataType="int"/>
</Columns>
</MSSqlServerSettingsSection>
<appSettings>
<!-- Serilog Settings -->
<add key="serilog:minimum-level" value="Verbose" />
<add key="serilog:using:Console" value="Serilog.Sinks.Console"/>
<add key="serilog:write-to:Console"/>
<add key="serilog:using:File" value="Serilog.Sinks.File" />
<add key="serilog:write-to:File"/>
<add key="serilog:write-to:File.path" value="{PATH}" />
<add key="serilog:write-to:File.rollingInterval" value="Day"/>
<add key="serilog:using:MSSqlServer" value="Serilog.Sinks.MSSqlServer" />
<add key="serilog:write-to:MSSqlServer.connectionString" value="Server=[SERVER];Database=[DATABASE];User Id=Serilog;Password=[PASSWORD];"/>
<add key="serilog:write-to:MSSqlServer.tableName" value="Logs"/>
<add key="serilog:write-to:MSSqlServer.autoCreateSqlTable" value="true"/>
</appSettings>
</configuration>
Calling Log.("{RunId}{Message}", _runId, _messageToShow) causes my File, Console AND MSSqlServer sinks to execute writing to these, if it doesn't exist the Logs table will be created, removing the Properties column and adding the RunId column. (If the table exists it doesn't do anything but write the data, still trying to work out how to handle that).
I'm still playing around with this (my next thing is to work out how to write {RunId} but not have it appear in the log, only written to the corresponding database field).
HTH,
Phil
I've a windows service that creates and hosts some jobs (today the number is 64, it can go up to 1400) to be executed as different schedules. Out of those 64 that I currently have say 30 should kick off at 4 AM and 4 PM every day, 10 should kick off at 3 AM and 3 PM every day and the remaining 14 should kick off at 5 AM and 5 PM everyday. I see that few of the jobs are being triggered properly according to the schedule and it is leaving several jobs behind and never triggering them.
When I looked at the crons expressions in the QRTZ_ tables and the Next fire time all looks good. I've had the following configurations and the below is my code to create and trigger the schedules. What must be going wrong and how can I figure that out.
IJobDetail objJobDet = JobBuilder.Create<AuditProcessor>()
.WithIdentity("Job-" + obj.Key.ToString(), AuditType.ToString())
.UsingJobData(obj.Key)
.Build();
ITrigger objJobTrigger = TriggerBuilder.Create()
.WithIdentity("Trigger-" + obj.Key.ToString(), AuditType.ToString())
.StartAt(new DateTimeOffset(startAtTime))
.WithCronSchedule(objCronExp[obj.Key], x => x.WithMisfireHandlingInstructionIgnoreMisfires())
.Build();
QrtzController.AddJobToScheduler(objJobDet, objJobTrigger);
Config:
<add key="QrtzSchedulerInstanceName" value="SignAuditInstance"/>
<add key="QrtzSchedulerInstanceId" value="SignAuditInstanceOne"/>
<add key="QrtzThreadPoolType" value="Quartz.Simpl.SimpleThreadPool, Quartz"/>
<add key="QrtzthreadCount" value="50"/>
<add key="QrtzThreadPriority" value="Normal"/>
<!--Job Store Properties-->
<add key="QrtZJobStoreMisfireThreshold" value="60000"/>
<add key="QrtzJobStoreType" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"/>
<add key="QrtzJobStoreUseProps" value="false"/>
<add key="QrtzJobStoreDataSource" value="default"/>
<add key="QrtzJobStoreClustered" value="false"/>
I've created an ASP.NET MVC website. Then I've created a class library named Site.Scheduler where I wanted to put all my triggers and jobs.
I've created a simple job for testing purposes
public class CurrencyRatesJob : IJob
{
private readonly IBudgetsRepository budgetsRepository;
public CurrencyRatesJob(IBudgetsRepository budgetsRepository)
{
this.budgetsRepository = budgetsRepository;
}
public void Execute(IJobExecutionContext context)
{
try
{
var budgets = new BudgetsDTO();
var user = new UserDTO();
budgets.Sum = 1;
budgets.Name = "Quartz";
user.Email = "email#g.com";
budgetsRepository.InsertBudget(budgets, user);
}
catch (Exception ex)
{
throw new Quartz.JobExecutionException(ex);
}
}
}
and a Job Scheduler
public class CurrencyRatesJobScheduler
{
public static void GetCurrencyRates()
{
try
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
IJobDetail job = JobBuilder.Create<CurrencyRatesJob>().Build();
ITrigger trigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule
(s =>
s.WithIntervalInSeconds(10)
.RepeatForever()
)
.Build();
scheduler.ScheduleJob(job, trigger);
}
catch (Exception ex)
{
ex.ToString();
}
}
}
To start the scheduler when application starts, I've added the following in Global.asax.cs
CurrencyRatesJobScheduler.GetCurrencyRates();
So after all that, I was expecting the job to execute every 10 seconds and insert all that info in the DB, but it doesn't do anything and I get no errors either.
Does anyone know what could be the issue?
EDIT:
So I've created all the necessary tables (executed the script from Quartz.NET) and I've added a new App.config file in my class library
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="quartz.scheduler.instanceName" value="MyQuartzScheduler" />
<add key="quartz.scheduler.instanceId" value="instance_one" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="1" />
<add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.dataSource" value="default" />
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" />
<add key="quartz.jobStore.lockHandler.type" value="Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz" />
<add key="quartz.jobStore.tablePrefix" value="QRTZ_" />
<add key="quartz.dataSource.default.connectionString" value="Server=(local);Database=My.Database;UID=User;PWD=Password" />
<add key="quartz.dataSource.default.provider" value="SqlServer-20" />
<add key="quartz.jobStore.useProperties" value="true" />
</appSettings>
</configuration>
Still no luck. Besides that, no triggers or jobs were stored in the DB.
You need to call
scheduler.Start();
First thing that stands out, you haven't started scheduler. So quartz is not running.
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
scheduler.Start();
After starting it the server will scan the db for job details/triggers and proceed accordingly.
However, you also want to add proper identities to your job detail/trigger. These are needed for quartz to create primary keys, otherwise you will get a SchedulerException.
IJobDetail job = JobBuilder.Create<CurrencyRatesJob>()
.WithIdentity("currencyJob", "group1")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.StartNow()
.WithIdentity("currencyJob", "group1")
.WithSimpleSchedule(s => s.WithIntervalInSeconds(10).RepeatForever())
.Build();
Regarding your config file, you need to declare an appropriate quartz section and add the settings there (by default this is where StdSchedulerFactory looks to create properties)
<configuration>
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</configSections>
<quartz>
<add key="quartz.scheduler.instanceName" value="MyQuartzScheduler" />
<add key="quartz.scheduler.instanceId" value="AUTO" />
...
</quartz>
</configuration>
i am also facing the same issue. i fixed the issue by placing config information in the app.config in the consuming application. As Class Library does not capable of running independently. It should be referenced by another app maybe console or web site. we need to place all config items in the consuming application this may be help. cheers. happy sharing
I'm trying to run Quartz.net server for my website maintance tasks.
I create Jobs and triggers in my WCF application (hosted on IIS). So they can be stored in database (SQL Server).
Now i can't understand ADO.NET Job Store.
Here is my web.config part for Quartz.net:
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler" />
</configSections>
<quartz>
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="Normal" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.type" value="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" />
<add key="quartz.jobStore.driverDelegateType" value="Quartz.Impl.AdoJobStore.**SqlServerDelegate**, Quartz" />
<add key="quartz.jobStore.tablePrefix" value="QRTZ_" />
<add key="quartz.jobStore.dataSource" value="ConnectionString" />
<add key="quartz.dataSource.ConnectionString.connectionString" value="Server=*;Database=*;Uid=*;Pwd=*" />
<add key="quartz.dataSource.ConnectionString.provider" value="SqlServer-20" />
<add key="quartz.scheduler.instanceName" value="PaymentService" />
<add key="quartz.jobStore.useProperties" value="true" />
</quartz>
And here is my global.asax:
public class Global : System.Web.HttpApplication
{
public static ISchedulerFactory Factory;
public static IScheduler Scheduler;
protected void Application_Start(Object sender, EventArgs e)
{
Factory = new StdSchedulerFactory();
Scheduler = Factory.GetScheduler();
JobKey JobKey = new JobKey("GetOrderInfoJob", "Project");
if (Scheduler.CheckExists(JobKey))
Scheduler.DeleteJob(JobKey);
IJobDetail job = JobBuilder.Create<PaymentServiceLogic>()
.WithIdentity(JobKey)
.StoreDurably()
.RequestRecovery()
.Build();
TriggerKey triggerKey = new TriggerKey("GetOrderInfoTrigger", "Project");
TriggerBuilder tb = TriggerBuilder.Create();
tb.WithIdentity(triggerKey );
tb.WithSimpleSchedule(a => a.WithIntervalInMinutes(1));
tb.StartNow();
tb.ForJob(job);
ITrigger trigger = tb.Build();
Scheduler.ScheduleJob(trigger);
Scheduler.Start();
}
}
Once i've started WCF service on localhost, QRTZ_JOB_DETAILS table in SQL Server for jobs had 1 entry, but no triggers.
I've tested this code a couple times and now no jobs are storing and therefore i have this exception:
Couldn't store trigger job: The job referenced by the trigger does not exist.
Is there some bug with job's building or AdoJobStore?
And the second question is about how to do correct shutdown in global.asax. Now i've decided this method:
protected void Application_End(object sender, EventArgs e)
{
ICollection<IScheduler> all = Factory.AllSchedulers;
foreach (IScheduler item in all)
{
item.Shutdown(true);
}
}
and implementing my own logging in Application_Error.
Note, sure whether it is a good idea to ask two questions in one, but the answer to your first question is you need to change
Scheduler.ScheduleJob(trigger);
to
Scheduler.ScheduleJob(job, trigger);
The former is when the job has previously been added to the scheduler, whereas the latter adds both the job and trigger at the same time.
I need scheduling functionality on my .NET MVC website and I came across Quartz.net library which can do exactly what I need.
The problem is I'm running my site on a hosting (GoDaddy) and when I added Quartz.net 2.0.1 to my project I've got "that assembly does not allow partially trusted callers" exception. After some research I found out that many people have the same problem and some solved it by removing Common.Logging library from Quartz.net.
I followed some of the advice and removed all references to Common.Logging but I still have problems. It looks like it's not enough and now I'm getting Inheritance security rules violated while overriding member exception, more details:
Inheritance security rules violated while overriding member:
Quartz.Util.DirtyFlagMap`2<TKey,TValue>.GetObjectData
(System.Runtime.Serialization.SerializationInfo,
System.Runtime.Serialization.StreamingContext)'.
Security accessibility of the overriding method must match the
security accessibility of the method being overriden.
It looks like I really need to change something in Quartz.net to make it work.
Has anyone run Quartz.net on medium trust? If so what needs to be done? May be someone can suggest some alternatives?
Steinar's answer sent me in the right direction. Sharing the steps here that got QuartZNet to work in a medium trust hosting environment.
QuartzNet initially ran into permissions issues on medium trust, we needed to the do the following to fix the issue
(1) Downloaded QuartzNet code ( 2.1.0.400 ) from github and build it after making the following changes to AssemblyInfo.cs
Replaced
#if !NET_40
[assembly: System.Security.AllowPartiallyTrustedCallers]
#endif
with
[assembly: AllowPartiallyTrustedCallers]
#if NET_40
[assembly: SecurityRules(SecurityRuleSet.Level1)]
#endif
(2) Downloaded C5 code (v 2.1) and built it with
[assembly: AllowPartiallyTrustedCallersAttribute()
Ensure C5 is compiled in the same .NET version as Qartznet.
(3) Added the quartz section to web.config within TGH, section had requirepermission set to false. Common logging section also had requirepermission set to false, also configured it to use Common.Logging.Simple.NoOpLoggerFactoryAdapter.
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" requirePermission="false" />
</sectionGroup>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.Simple.NoOpLoggerFactoryAdapter, Common.Logging">
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="level" value="OFF" />
<arg key="dateTimeFormat" value="HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common>
<quartz>
<add key="quartz.scheduler.instanceName" value="QuartzScheduler" />
<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="2" />
<add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />
</quartz>
(4) Initialised the scheduler using constructor with namecollection as the parameter, namecollection was the quartz section picked up from web.config.
In global.asax
QuartzScheduler.Start();
The class
public class QuartzScheduler
{
public static void Start()
{
ISchedulerFactory schedulerFactory = new StdSchedulerFactory((NameValueCollection)ConfigurationManager.GetSection("quartz"));
IScheduler scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
IJobDetail inviteRequestProcessor = new JobDetailImpl("ProcessInviteRequest", null, typeof(InviteRequestJob));
IDailyTimeIntervalTrigger trigger = new DailyTimeIntervalTriggerImpl("Invite Request Trigger", Quartz.TimeOfDay.HourMinuteAndSecondOfDay(0, 0, 0), Quartz.TimeOfDay.HourMinuteAndSecondOfDay(23, 23, 59), Quartz.IntervalUnit.Second, 1);
scheduler.ScheduleJob(inviteRequestProcessor, trigger);
}
}
public class InviteRequestJob : IJob
{
public void Execute(IJobExecutionContext context)
{
RequestInvite.ProcessInviteRequests();
}
}
I recommend building Common.Logging yourself rather than removing it from the project. You can get the latest source from http://netcommon.sourceforge.net/downloads.html.
I guess the second problem had to do with that the C5.dll wasn't trusted either. I would also just build that myself. The source can be found here: http://www.itu.dk/research/c5/.
Although there are other options than building the dlls (http://stackoverflow.com/questions/3072359/unblocking-a-dll-on-a-company-machine-how) I personally prefer to build the dlls myself unless I absolutely trust the downloaded product.