How can I call TriggerJob with custom jobDataMap? - quartz.net

I've got a Quartz.NET job which I set up as follows:
var jobKey = new JobKey("JobName", "JobGroup");
var triggerKey = new TriggerKey("JobName", "JobGroup");
var jobData = new JobDataMap();
jobData.Add("SomeKey", "OriginalValue");
var jobDetail = JobBuilder.Create<JobClass>()
.WithIdentity(jobKey)
.StoreDurably()
.UsingJobData(jobData)
.Build();
Scheduler.AddJob(jobDetail, true);
var triggerDetail = TriggerBuilder.Create()
.WithIdentity(triggerKey)
.StartNow()
.WithDailyTimeIntervalSchedule(x => x.OnEveryDay()
.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(04, 07))
.EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(06, 07))
.WithMisfireHandlingInstructionFireAndProceed())
.ForJob(jobKey)
.Build();
Scheduler.ScheduleJob(triggerDetail);
I am trying to manually trigger that job using the following code:
var jobData = new JobDataMap();
jobData.Add("SomeKey", "SomeValue");
TaskScheduler.Scheduler.TriggerJob(new Quartz.JobKey("JobName", "JobGroup"), jobData);
When I run the manual trigger this piece of code, the value in
context.JobDetail.JobDataMap["SomeKey"]
is
"OriginalValue"
rather than
"SomeValue"
as I would expect. What am I doing wrong?

Both trigger and job have jobData.
Line TaskScheduler.Scheduler.TriggerJob(new Quartz.JobKey("JobName", "JobGroup"), jobData);
assigns jobData to trigger. You can see 'SomeValue' in context.Trigger.JobDataMap["SomeKey"]

Using a reference type works:
//A simple class used here only for storing a string value:
public class SimpleDTO
{
public string Value { get; set; }
}
void Work() {
var dto = new SimpleDTO();
dto.Value = "OriginalValue";
JobDataMap data = new JobDataMap();
data["Key"] = dto;
TaskScheduler.Scheduler.TriggerJob(new Quartz.JobKey("JobName", "JobGroup"), jobData);
//read modified new value:
var resultDto = (SimpleDTO)data["Key"];
Assert.AreEqual("NewValue", resultDto.Value);
}
public void Execute(IJobExecutionContext context)
{
var simpleDTO = (SimpleDTO)context.MergedJobDataMap["SomeKey"];
//set a new value:
simpleDTO.Value = "NewValue";
}

Related

Unable to cast object of type 'Microsoft.Graph.CalendarEventsCollectionPage' to type 'System.Collections.Generic.IEnumerable`

My controller code is lke this,
public async Task<IEnumerable<CalendarEvent>> Get()
{
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "xxxxx";
var clientId = "xxxxxx";
var clientSecret = "xxxx";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes);
if (User == null!)
{
var user = await graphServiceClient.Users["xxxxx.com"].Calendar
.Events
.Request()
.Select("subject,body,bodyPreview,organizer,attendees,start,end,location")
.GetAsync();
return (CalendarEvent)user;
}
}
Iam getting an error like
Unable to cast object of type 'Microsoft.Graph.CalendarEventsCollectionPage' to type 'System.Collections.Generic.IEnumerable'
I need query that sholud be given in controller.
It's not exactly clear what you are trying to achieve but you can't convert CalendarEventsCollectionPage to IEnumerable. I am assuming that you want to return all events of specific user.
public async Task<List<Event>> GetEventsOfUser(string userId)
{
var events = new List<Event>();
var eventsPages = _client.Users[userId].Calendar.Events.Request()
.Select("subject,body,bodyPreview,organizer,attendees,start,end,location");
while (eventsPages != null)
{
var current = await eventsPages.GetAsync();
events.AddRange(current.CurrentPage);
eventsPages = current.NextPageRequest;
}
return events;
}
You need to fetch every page with NextPageRequest in order to get all events.

Using Postal and Hangfire in Subsite

I have been trying to use Postal on my MVC5 site. When I host my webpage a subsite ie, http://localhost/Subsite I am receiving the error
The virtual path '/' maps to another application, which is not allowed
I have debugged it down to when the ControllerContext is being created the HttpContext isn't getting set correctly. Since I'm running Postal from Hangfire the HttpContext.Current is always null. Postal creates the ContollerContext using the code below.
ControllerContext CreateControllerContext()
{
// A dummy HttpContextBase that is enough to allow the view to be rendered.
var httpContext = new HttpContextWrapper(
new HttpContext(
new HttpRequest("", UrlRoot(), ""),
new HttpResponse(TextWriter.Null)
)
);
var routeData = new RouteData();
routeData.Values["controller"] = EmailViewDirectoryName;
var requestContext = new RequestContext(httpContext, routeData);
var stubController = new StubController();
var controllerContext = new ControllerContext(requestContext, stubController);
stubController.ControllerContext = controllerContext;
return controllerContext;
}
string UrlRoot()
{
var httpContext = HttpContext.Current;
if (httpContext == null)
{
return "http://localhost";
}
return httpContext.Request.Url.GetLeftPart(UriPartial.Authority) +
httpContext.Request.ApplicationPath;
}
How can I specify the UrlRoot so that instead of pulling the default of localhost to pull it based on my subsite?
I followed the directions here http://docs.hangfire.io/en/latest/tutorials/send-email.html to send my email. The method in the tutorial is below
public static void NotifyNewComment(int commentId)
{
// Prepare Postal classes to work outside of ASP.NET request
var viewsPath = Path.GetFullPath(HostingEnvironment.MapPath(#"~/Views/Emails"));
var engines = new ViewEngineCollection();
engines.Add(new FileSystemRazorViewEngine(viewsPath));
var emailService = new EmailService(engines);
// Get comment and send a notification.
using (var db = new MailerDbContext())
{
var comment = db.Comments.Find(commentId);
var email = new NewCommentEmail
{
To = "yourmail#example.com",
UserName = comment.UserName,
Comment = comment.Text
};
emailService.Send(email);
}
}
I found the issue was that the FileSystemRazorViewEngine was not being used bty postal. To get the this to work I had to make sure that the FileSystemRazorViewEngine was the first engine in the available. I then removed it because I did not want it to be the default engine. Below is my updated method.
public static void NotifyNewComment(int commentId)
{
// Prepare Postal classes to work outside of ASP.NET request
var viewsPath = Path.GetFullPath(HostingEnvironment.MapPath(#"~/Views/Emails"));
var eng = new FileSystemRazorViewEngine(viewsPath));
ViewEngines.Engines.Insert(0, eng);
var emailService = new EmailService(engines);
// Get comment and send a notification.
using (var db = new MailerDbContext())
{
var comment = db.Comments.Find(commentId);
var email = new NewCommentEmail
{
To = "yourmail#example.com",
UserName = comment.UserName,
Comment = comment.Text
};
emailService.Send(email);
ViewEngines.Engines.RemoveAt(0)
}
}
Below is another possible solution that I think is more elegant than above. It also resolves an issue that appears when accessing the MVC application while the background process is being executed.
public static void SendTypedEmailBackground()
{
try
{
var engines = new ViewEngineCollection();
var viewsPath = Path.GetFullPath(HostingEnvironment.MapPath(#"~/Views/Emails"));
var eng = new FileSystemRazorViewEngine(viewsPath);
engines.Add(eng);
var email = new WebApplication1.Controllers.EmailController.TypedEmail();
email.Date = DateTime.UtcNow.ToString();
IEmailService service = new Postal.EmailService(engines);
service.Send(email);
}
catch(Exception ex)
{
throw ex;
}
}

What kind of object has to be passed for JsonResult in MVC.Net

So I'm passing a custom class to my controller and it seems that the JsonResult is not properly passed.
What bothers me is that (also the fullcalendar wont read the json) the console.log which I have in my view prints the path to the function (wtf?) instead of what Json shoul return
This is my code:
public JsonResult GetCalendarEvents()
{
var eventList = BusinessLayer.Event.getAllEvents();
return Json(eventList.ToArray(), JsonRequestBehavior.AllowGet);
}
What kind of object has to be passed for this to work?
My evenList is of type List<Event> from here:
public static String ListToString(List<Event> evs)
{
String ret = "";
foreach (var ev in evs)
{
ret += ev.ToString() + "\n";
}
return ret;
}
public static List<Event> getAllEvents()
{
List<DataLayer.Event> dbEvents = DataApi.db.Event.ToList();
List<Event> returnEvents = new List<Event>();
foreach (DataLayer.Event oneEvent in dbEvents)
{
Event newEvent = new Event
{
ID = oneEvent.IDEvent,
userID = oneEvent.UserID,
projectID = oneEvent.ProjectID,
jobtypeID = oneEvent.JobTypeID,
taskID = oneEvent.TaskID,
ticketID = oneEvent.TicketID,
loccoID = oneEvent.LoccoID,
startTime = oneEvent.StartTime,
endTime = oneEvent.EndTime,
shiftFrom = oneEvent.ShiftFrom,
shiftTo = oneEvent.ShiftTo,
description = oneEvent.Description,
billable = oneEvent.Billable
};
returnEvents.Add(newEvent);
}
return returnEvents;
}
I tried displaying the events in fullcalendar:
$('#calendar').fullCalendar({
header: {
left: 'title',
center: '',
right: 'prev,next today basicDay,basicWeek,month',
},
//events: "/Calendar/GetEvents/", // not implemented
events: "#Url.Action("GetCalendarEvents/")",
and outputing the result to console:
console.log("#Url.Action("GetCalendarEvents/")");
but I get:
VM84 Index:83 /Calendar/GetCalendarEvents/
fullcalendar.min.js:6 Uncaught TypeError: Cannot read property 'hasTime' of undefined
It looks like you're missing some required fields. If you look at the documentation, title, start are required. Try setting these in the class to start with and build from that...
public static List<Event> getAllEvents()
{
List<DataLayer.Event> dbEvents = DataApi.db.Event.ToList();
List<Event> returnEvents = new List<Event>();
foreach (DataLayer.Event oneEvent in dbEvents)
{
Event newEvent = new Event
{
start = oneEvent.StartTime,
title = oneEvent.Description // you may need to add this to your Event class.
};
returnEvents.Add(newEvent);
}
return returnEvents;
}
Also, instead of using console to log the Json, use Fiddler or Chrome Advanced Tools

Quartz: Why does this Trigger not fire?

I am using quartz.NET in my project. I have the following problem. I want to run a Scheduled task everyday at 23 o'clock and I am using this code to do that:
public class HelloJob : IJob
{
public void Execute(IJobExecutionContext context)
{
//Download file
}
}
public interface ISchedule
{
void Run();
}
public class HelloSchedule : ISchedule
{
public void Run()
{
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("job1")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.ForJob(job)
.WithIdentity("trigger1")
.StartNow()
.WithCronSchedule("0 0 23 ? * MON-FRI *")
.Build();
ISchedulerFactory sf = new StdSchedulerFactory();
IScheduler sc = sf.GetScheduler();
sc.ScheduleJob(job, trigger);
sc.Start();
}
}
but unfortunately it's not firing.
How can I know what the problem is and solve it?
Thanks for your advice
Your job will fire at 11pm.
You can check the next fire time for you job using this code:
private static DateTime getNextFireTimeForJob(IScheduler scheduler, string jobName, string groupName = "")
{
JobKey jobKey = null;
if (!string.IsNullOrEmpty(groupName))
{
jobKey = new JobKey(jobName, groupName);
}
else
{
jobKey = new JobKey(jobName);
}
DateTime nextFireTime = DateTime.MinValue;
bool isJobExisting = Scheduler.CheckExists(jobKey);
if (isJobExisting)
{
var detail = scheduler.GetJobDetail(jobKey);
var triggers = scheduler.GetTriggersOfJob(jobKey);
var myTrigger = triggers.Where(f => f.Key.Name == "SecondTrigger").SingleOrDefault();
if (triggers.Count > 0)
{
var nextFireTimeUtc = triggers[0].GetNextFireTimeUtc();
nextFireTime = TimeZone.CurrentTimeZone.ToLocalTime(nextFireTimeUtc.Value.DateTime);
}
}
return (nextFireTime);
}
and get the info using this:
var nextFireTime = getNextFireTimeForJob(sc, "job1");
Make sure your HelloJob implements IJob.
If you're integrating your Quartz.net scheduler in a WinApp make sure it's created singleton cause it might be destroyed when it goes out of scope.
I test my jobs in a console application and use Console.ReadLine(); to wait the jobs execution.
Use org.Quartz and try this:
JobDetail job = new JobDetail();
job.setName(Constants.JOB_NAME);
job.setJobClass(YOUR_JOB_CLASS.class);
CronTrigger trigger = new CronTrigger();
trigger.setName(Constants.TRIGGER_NAME);
trigger.setCronExpression("0 0 23 ? * MON-FRI *");
// schedule it
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(job, trigger);
Your Job class should implement org.Quartz.Job interface and override its execute method which does the actual thing the job needs to perform.

Optimizing GraphClient Connection?

Below you will see my GraphOperations class (written in C# using Neo4jClient) that performs basic Neo4j graph operations. The GraphGetConnection() method connects to Neo4j and returns clientConnection and my CreateNode() method created a node and returns its node reference.
Now in that method you will see that Im going GraphOperations graphOp = new GraphOperations(); and then clientConnection= graphOp.GraphConnection();.
Is this the right way to do this?
Do I call a connection every time I want to perform an operation?
How do I optimize this code below? I want to create a method for each CRUD operation and want to find the best way to do this.
I hope the question is clear enough?
using Neo4jClient;
public class GraphOperations
{
GraphClient clientConnection;
public GraphClient GraphGetConnection()
{
clientConnection = new GraphClient(new Uri("http://localhost:7474/db/data"));
clientConnection.Connect();
return clientConnection;
}
public long GraphCreateNode(string type, string name, string customerName, string documentReference, int newVersionNumber)
{
Guid nodeGuid = Guid.NewGuid();
System.DateTime dateTime = System.DateTime.Now;
string timeStamp = String.Format("{0:dd MMMM yyyy HH:mm:ss}", dateTime);
GraphOperations graphOp = new GraphOperations();
clientConnection = graphOp.GraphGetConnection();
var createNode = clientConnection.Create(new VersionNode()
{
GUID = nodeGuid.ToString(),
Name = name,
Type = type,
DocumentReference = documentReference,
DateTimeCreated = timeStamp,
Version = newVersionNumber
});
return createNode.Id;
}
}
Well, you are already storing the GraphClient in the GraphOperations class, and as your GraphCreateNode method isn't static, you could just use that field, so your GraphCreateNode method becomes something like:
public long GraphCreateNode(string type, string name, string customerName, string documentReference, int newVersionNumber)
{
/* CODE */
this.GraphGetConnection(); //Don't care or need the return from GraphGetConnection
//From then on just:
clientConnection.DOSTUFF ....
/* CODE */
Personally, I would change a few things to make life a bit easier for yourself:
public class GraphOperations
{
private GraphClient clientConnection;
private void InitializeGraphClient()
{
if(this.clientConnection != null)
return;
this.clientConnection = new GraphClient(new Uri("http://localhost:7474/db/data"));
this.clientConnection.Connect();
}
public NodeReference CreateNode(/*parameters*/)
{
InitializeGraphClient();
Guid nodeGuid = Guid.NewGuid();
System.DateTime dateTime = System.DateTime.Now;
string timeStamp = String.Format("{0:dd MMMM yyyy HH:mm:ss}", dateTime);
var createNode = this.clientConnection.Create(
new VersionNode()
{
GUID = nodeGuid.ToString(),
Name = name,
Type = type,
DocumentReference = documentReference,
DateTimeCreated = timeStamp,
Version = newVersionNumber
});
return createNode.Id;
}
}
In each method (CRUD-wise) you'll call InitializeGraphClient and that will make sure the connection is there. The other way (and this might be preferable) is to stick that initialization into the constructor for GraphOperations:
public class GraphOperations
{
private readonly GraphClient clientConnection;
public GraphOperations()
{
this.clientConnection = new GraphClient(new Uri("http://localhost:7474/db/data"));
this.clientConnection.Connect();
}
public NodeReference CreateNode(/*parameters*/)
{
Guid nodeGuid = Guid.NewGuid();
System.DateTime dateTime = System.DateTime.Now;
string timeStamp = String.Format("{0:dd MMMM yyyy HH:mm:ss}", dateTime);
var createNode = this.clientConnection.Create(
new VersionNode()
{
GUID = nodeGuid.ToString(),
Name = name,
Type = type,
DocumentReference = documentReference,
DateTimeCreated = timeStamp,
Version = newVersionNumber
});
return createNode.Id;
}
}
and using that you should always know that the GraphClient instance will be there, which means your CRUD methods can focus on doing CRUD and not initializing the GraphClient. There is the potential with this that an Exception could be thrown from the constructor, but as to whether that is a bad thing or not is personal preference.

Resources