BradVin's .Net Blog

Code, snippets, controls, utils, etc. Basically all things .net

Creating vCalendars programmatically in C#

Have you ever wanted to send out meeting requests from your code? Well I wanted to do just that today. And I didnt want to reference the Outlook dll's. I simply wanted to send an email to an attendee's email address.

After much searching and alot of useless code I found a gem at http://chuckdotnet.blogspot.com. I took the code there and modified it slightly so that you can send the meeting request to multiple recipients (attendees) at once.

below is the method that creates the system.net.mailmessage object with all the necessary alternateviews, so that when opened in outlook it behaves exactly like a meeting request that was sent from within outlook. remember to add using System.Net.Mail;using System.Net.Mime; to the top of your code.


public static MailMessage CreateMeetingRequest(DateTime start, DateTime end, string subject, string summary,
string location, string organizerName, string organizerEmail, string attendeeName, string attendeeEmail)
{
MailAddressCollection col = new MailAddressCollection();
col.Add(new MailAddress(attendeeEmail, attendeeName));
return CreateMeetingRequest(start, end, subject, summary, location, organizerName, organizerEmail, col);
}

public static MailMessage CreateMeetingRequest(DateTime start, DateTime end, string subject, string summary,
string location, string organizerName, string organizerEmail, MailAddressCollection attendeeList)
{
MailMessage msg = new MailMessage();

// Set up the different mime types contained in the message
System.Net.Mime.ContentType textType = new System.Net.Mime.ContentType("text/plain");
System.Net.Mime.ContentType HTMLType = new System.Net.Mime.ContentType("text/html");
System.Net.Mime.ContentType calendarType = new System.Net.Mime.ContentType("text/calendar");

// Add parameters to the calendar header
calendarType.Parameters.Add("method", "REQUEST");
calendarType.Parameters.Add("name", "meeting.ics");

// Create message body parts
// create the Body in text format
string bodyText = "Type:Single Meeting\r\nOrganizer: {0}\r\nStart Time:{1}\r\nEnd Time:{2}\r\nTime Zone:{3}\r\nLocation: {4}\r\n\r\n*~*~*~*~*~*~*~*~*~*\r\n\r\n{5}";
bodyText = string.Format(bodyText,
organizerName,
start.ToLongDateString() + " " + start.ToLongTimeString(),
end.ToLongDateString() + " " + end.ToLongTimeString(),
System.TimeZone.CurrentTimeZone.StandardName,
location,
summary);

AlternateView textView = AlternateView.CreateAlternateViewFromString(bodyText, textType);
msg.AlternateViews.Add(textView);

//create the Body in HTML format
string bodyHTML = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\r\n<HTML>\r\n<HEAD>\r\n<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=utf-8\">\r\n<META NAME=\"Generator\" CONTENT=\"MS Exchange Server version 6.5.7652.24\">\r\n<TITLE>{0}</TITLE>\r\n</HEAD>\r\n<BODY>\r\n<!-- Converted from text/plain format -->\r\n<P><FONT SIZE=2>Type:Single Meeting<BR>\r\nOrganizer:{1}<BR>\r\nStart Time:{2}<BR>\r\nEnd Time:{3}<BR>\r\nTime Zone:{4}<BR>\r\nLocation:{5}<BR>\r\n<BR>\r\n*~*~*~*~*~*~*~*~*~*<BR>\r\n<BR>\r\n{6}<BR>\r\n</FONT>\r\n</P>\r\n\r\n</BODY>\r\n</HTML>";
bodyHTML = string.Format(bodyHTML,
summary,
organizerName,
start.ToLongDateString() + " " + start.ToLongTimeString(),
end.ToLongDateString() + " " + end.ToLongTimeString(),
System.TimeZone.CurrentTimeZone.StandardName,
location,
summary);

AlternateView HTMLView = AlternateView.CreateAlternateViewFromString(bodyHTML, HTMLType);
msg.AlternateViews.Add(HTMLView);

//create the Body in VCALENDAR format
string calDateFormat = "yyyyMMddTHHmmssZ";
string bodyCalendar = "BEGIN:VCALENDAR\r\nMETHOD:REQUEST\r\nPRODID:Microsoft CDO for Microsoft Exchange\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:(GMT-06.00) Central Time (US & Canada)\r\nX-MICROSOFT-CDO-TZID:11\r\nBEGIN:STANDARD\r\nDTSTART:16010101T020000\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0600\r\nRRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:16010101T020000\r\nTZOFFSETFROM:-0600\r\nTZOFFSETTO:-0500\r\nRRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTAMP:{8}\r\nDTSTART:{0}\r\nSUMMARY:{7}\r\nUID:{5}\r\nATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=\"{9}\":MAILTO:{9}\r\nACTION;RSVP=TRUE;CN=\"{4}\":MAILTO:{4}\r\nORGANIZER;CN=\"{3}\":mailto:{4}\r\nLOCATION:{2}\r\nDTEND:{1}\r\nDESCRIPTION:{7}\\N\r\nSEQUENCE:1\r\nPRIORITY:5\r\nCLASS:\r\nCREATED:{8}\r\nLAST-MODIFIED:{8}\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nX-MICROSOFT-CDO-BUSYSTATUS:BUSY\r\nX-MICROSOFT-CDO-INSTTYPE:0\r\nX-MICROSOFT-CDO-INTENDEDSTATUS:BUSY\r\nX-MICROSOFT-CDO-ALLDAYEVENT:FALSE\r\nX-MICROSOFT-CDO-IMPORTANCE:1\r\nX-MICROSOFT-CDO-OWNERAPPTID:-1\r\nX-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE:{8}\r\nX-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE:{8}\r\nBEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:REMINDER\r\nTRIGGER;RELATED=START:-PT00H15M00S\r\nEND:VALARM\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
bodyCalendar = string.Format(bodyCalendar,
start.ToUniversalTime().ToString(calDateFormat),
end.ToUniversalTime().ToString(calDateFormat),
location,
organizerName,
organizerEmail,
Guid.NewGuid().ToString("B"),
summary,
subject,
DateTime.Now.ToUniversalTime().ToString(calDateFormat),
attendeeList.ToString());

AlternateView calendarView = AlternateView.CreateAlternateViewFromString(bodyCalendar, calendarType);
calendarView.TransferEncoding = TransferEncoding.SevenBit;
msg.AlternateViews.Add(calendarView);

// Adress the message
msg.From = new MailAddress(organizerEmail);
foreach (MailAddress attendee in attendeeList)
{
msg.To.Add(attendee);
}
msg.Subject = subject;

return msg;
}
Posted: Jan 16 2008, 08:29 PM by bradvin | with 11 comment(s)
Filed under: ,

Comments

Trumpi's blog said:

Software Rhino ETL 2.0 Announcing Sandcastle Version 2.4.10115 Agile Patterns of Agile Adoption by Mike

# January 16, 2008 4:29 PM

Dave T said:

Nice snippet brad, but have you thought about using the @ symbol to escape your string literals?

Then, instead of having your entire vCalendar template in one line using \r\n, you could have literal new lines   and would be able to see the format indicies e.g. "{0}" much better, as well as the various names of all the 'properties'.

Like so:

string bodyCalendar = @"BEGIN:VCALENDAR

METHOD:REQUEST

PRODID:Microsoft CDO for Microsoft Exchange

VERSION:2.0

BEGIN:VTIMEZONE

TZID:(GMT-06.00) Central Time (US & Canada)

X-MICROSOFT-CDO-TZID:11

BEGIN:STANDARD

DTSTART:16010101T020000

TZOFFSETFROM:-0500

TZOFFSETTO:-0600

RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU

END:STANDARD

BEGIN:DAYLIGHT

DTSTART:16010101T020000

TZOFFSETFROM:-0600

TZOFFSETTO:-0500

RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU

END:DAYLIGHT

END:VTIMEZONE

BEGIN:VEVENT

DTSTAMP:{8}

DTSTART:{0}

SUMMARY:{7}

UID:{5}

ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=""{9}"":MAILTO:{9}

ACTION;RSVP=TRUE;CN=""{4}"":MAILTO:{4}

ORGANIZER;CN=""{3}"":mailto:{4}

LOCATION:{2}

DTEND:{1}

DESCRIPTION:{7}\\N

SEQUENCE:1

PRIORITY:5

CLASS:

CREATED:{8}

LAST-MODIFIED:{8}

STATUS:CONFIRMED

TRANSP:OPAQUE

X-MICROSOFT-CDO-BUSYSTATUS:BUSY

X-MICROSOFT-CDO-INSTTYPE:0

X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY

X-MICROSOFT-CDO-ALLDAYEVENT:FALSE

X-MICROSOFT-CDO-IMPORTANCE:1

X-MICROSOFT-CDO-OWNERAPPTID:-1

X-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE:{8}

X-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE:{8}

BEGIN:VALARM

ACTION:DISPLAY

DESCRIPTION:REMINDER

TRIGGER;RELATED=START:-PT00H15M00S

END:VALARM

END:VEVENT

END:VCALENDAR";

# January 16, 2008 7:12 PM

bradvin said:

good suggestion Dave - i will update the code later today

# January 17, 2008 3:39 AM

Christopher Steen said:

Link Listing - January 16, 2008

# January 17, 2008 8:01 AM

Christopher Steen said:

Announcements .NET Framework Library Source Code now available [Via: ScottGu ] Announcing ALT.NET Open...

# January 17, 2008 8:02 AM

Shashidhar Reddy C said:

Excellent article. Working fine...

But how to add the same in Sender's calender?

# April 21, 2008 2:51 AM

Vijaya Bhaskar K said:

Good Article.

# April 21, 2008 8:31 AM

Mike said:

What version of the framework does this work in? I've tried it using 2.0 but it doesn't work for me.

# May 2, 2008 3:26 PM

Bob said:

Mike,

I got the original code to work in C# 2.0. However, when I used Dave T's code for the bodyCalendar Outlook did not recognize the Calendar appointment.

So use the code as it is originally given.

Thanks to the author, this will help our business run smoother.

# May 12, 2008 12:07 PM

Bob said:

I'm having issues with Vista/Outlook 2007 users. They are not receiving the invitations at all (not even text/html format). We are on an Exchange server if that makes any difference.

XP&Outlook 2003 work perfectly though.

# May 19, 2008 12:54 PM

Karim said:

Excellent. Thanks a lot for sharing.

# May 21, 2008 10:15 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)