The 2 most important things to note is that when working with any RadScheduler
- The backend implementation of the RadScheduler is NOT consistent across telerik platforms.
- Kendo UI does NOT support any type server-side processing
Because of the above, I have spent much time struggling with processing the server-side when using the Kendo UI RadScheduler. I saw the recommendations about using ASP.NET RadScheduler, or WinForms RadScheduler, DDay.iCal or iCal.NET.
One of the most frustrating things was the backend storage of the recurrence string did not plug/play into other RadScheduler platforms directly: recurrence string differences, class names, methods, etc. (#1 issue above)
In many forum answers, it just says use DDay.ICal or iCal.NET but there is no sample code. Well here it is for DDay.iCal! I was able to get DDay.iCal to do server-side processing of recurring events. I offer my code up to the community for review and use. Use at own risk.
Please note that I modified that standard Kendo UI backend database structure to include an “Estimated End Date”. It is NULL when the appointment “never” ends. I calculate the value during the appointment save using settings from the recurrence string. As long as I estimate correctly or even if I over-estimate, I am in the clear with my code.
Also note, that my appointments can have multiple people tied to it. Thus, you will see a final “foreach” loop that will flatten out the appointments per person.
My primary goal was to get a list of appointments, per person, within a specified time range so i can send reminder emails.
public
List<AppointmentItem> GetAppointments(
DateTime startDate, DateTime endDate)
{
var results =
new
List<AppointmentItem>();
var appts = _dataContext.tblAppointments
.Where(w =>
w.StartDateTime <= endDate
&& (
(w.EstimatedEndDateTime ==
null
)
// this gets the "never" ends entries
||
(w.EstimatedEndDateTime.Value >= w.StartDateTime)
)
)
.ToList();
foreach
(var appt
in
appts)
{
IICalendar iCal =
new
iCalendar();
IEvent eventHolder = iCal.Create<Event>();
eventHolder.Start =
new
iCalDateTime(appt.StartDateTime);
eventHolder.End =
new
iCalDateTime(appt.EndDateTime);
if
(!appt.RecurrenceAppointmentId.HasValue)
{
var recurPattern =
new
RecurrencePattern($
"RRULE:{appt.RecurrenceRule}"
);
eventHolder.RecurrenceRules.Add(recurPattern);
}
if
(!
string
.IsNullOrEmpty(appt.RecurrenceException))
{
var periodList =
new
PeriodList();
var allDates = appt.RecurrenceException.Split(
char
.Parse(
","
));
foreach
(var item
in
allDates)
periodList.Add(
new
iCalDateTime(DateTime.ParseExact(item,
"yyyyMMddTHHmmssZ"
, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)));
eventHolder.ExceptionDates.Add(periodList);
}
//if seems GetOccurrences does BETWEEN check, non-inclusive so i have to widen the dates a little bit.
foreach
(var occurrence
in
iCal.GetOccurrences(startDate.AddMilliseconds(-1), endDate.AddDays(1)))
{
results.AddRange(appt.tblAppointmentStudents.Select(student =>
new
AppointmentItem
{
AppointmentName = appt.AppointmentName,
AppointmentNotes = appt.AppointmentDetails,
EndTime = occurrence.Period.EndTime.Date,
StartTime = occurrence.Period.StartTime.Date,
StudentId = student.StudentId
}));
}
}
//results = results.OrderBy(t => t.StartTime).ToList();
return
results;
}
The AppointmentItem class:
public
class
AppointmentItem
{
public
int
StudentId {
get
;
set
; }
public
string
AppointmentName {
get
;
set
; }
public
string
AppointmentNotes {
get
;
set
; }
public
DateTime StartTime {
get
;
set
; }
public
DateTime EndTime {
get
;
set
; }
}
The function call
testing.GetAppointments(
new
DateTime(2016,12,15),
new
DateTime(2016, 12, 21));
Database Table tblAppointment
CREATE
TABLE
[dbo].[tblAppointment]
(
[AppointmentId] [
int
]
NOT
NULL
IDENTITY(1, 1),
[AppointmentName] [
varchar
] (500)
COLLATE
SQL_Latin1_General_CP1_CI_AS
NOT
NULL
,
[AppointmentDetails] [
varchar
] (
max
)
COLLATE
SQL_Latin1_General_CP1_CI_AS
NOT
NULL
,
[EndDateTime] [datetime]
NOT
NULL
,
[StartDateTime] [datetime]
NOT
NULL
,
[EstimatedEndDateTime] [datetime2]
NULL
,
[RecurrenceAppointmentId] [
int
]
NULL
,
[RecurrenceException] [
varchar
] (
max
)
COLLATE
SQL_Latin1_General_CP1_CI_AS
NULL
,
[RecurrenceRule] [
varchar
] (1024)
COLLATE
SQL_Latin1_General_CP1_CI_AS
NULL
)
ON
[
PRIMARY
] TEXTIMAGE_ON [
PRIMARY
]
GO
ALTER
TABLE
[dbo].[tblAppointment]
ADD
CONSTRAINT
[PK_tblAppointment]
PRIMARY
KEY
CLUSTERED ([AppointmentId])
ON
[
PRIMARY
]
GO
ALTER
TABLE
[dbo].[tblAppointment]
ADD
CONSTRAINT
[FK_tblAppointment_tblAppointment]
FOREIGN
KEY
([RecurrenceAppointmentId])
REFERENCES
[dbo].[tblAppointment] ([AppointmentId])
GO
Warning: i am using a beta candidate of DDay.iCal. After going through each type pattern (daily, weekly, monthly, yearly) and each type of pattern (until, first day of, exact day, etc.) I did not encounter any problems.
The only problem is when the appointment is set up "oddly". By that i mean this by way of example. Appointment occurs the 15th of every month. You set the initial date on the 20th of January. Kendo UI RadScheduler correctly does NOT show an entry on Jan 15 or Jan 20. However, DDay will have an occurrence on Jan 20 for your "15th of the month" event. What it boils down to is setting up the appointment skewed from pattern. It is a small risk in general in my application. The worst case scenario is that an even shows when it should not. Probably better than not showing it when it should.
Happy Programming!