From Wikipedia: iCalendar is a computer file format which allows internet users to send meeting requests and tasks to other internet users, via email, or sharing files with an .ics extension. Recipients of the iCalendar data file (with supporting software, such as an email client or calendar application) can respond to the sender easily or counter propose another meeting date/time.
My goal for this solution was to allow the users of my web application to add events to their Microsoft Outlook calendar. Using the iCalendar format makes the solution work with a lot of calendar applications.
So what do we need to make this work?
First I created a two classes. An Event class that contains information about the event and an iCalendar class that contains information about the calendar and a list of Events.
public class iCalendar
{
private const string DEFAULT_DATEFORMAT = "yyyyMMdd\\THHmmss\\Z";
private const string ALLDAY_DATEFORMAT = "yyyyMMdd";
public iCalendar()
{
this.Events = new List<Event>();
}
public List<Event> Events { get; set; }
public string Name { get; set; }
public string Description { get; set; }
//Helper methods to format the DateTime values.
//These are used from the MVC view.
public string GetTimeString(DateTime time)
{
return GetTimeString(time, false);
}
public string GetTimeString(DateTime time, bool allDay)
{
if (allDay)
return ";VALUE=DATE:" + time.ToUniversalTime().ToString(ALLDAY_DATEFORMAT);
else
return ":" + time.ToUniversalTime().ToString(DEFAULT_DATEFORMAT);
}
}
public class Event
{
public DateTime StartTime { get; set; }
public DateTime EndTime {get; set; }
public string Title { get; set; }
public string Location { get; set; }
public string Description { get; set; }
public string HtmlDescription { get; set; }
public bool AllDay { get; set; }
public bool Reminder { get; set; }
//A unique identifier for the event.
//If it's not set we return a new Guid value.
public string UID
{
get
{
if (string.IsNullOrEmpty(this.uid))
return Guid.NewGuid().ToString();
else
return this.uid;
}
set { this.uid = value; }
}
private string uid;
}
As you can see these classes are simple property containers without any real logic. To save space i removed some logic that returned default values if a property was not set. Similar to the UID property.
The View
Now we almost have our full implementation. All we need is a ASP.NET MVC View that renders the iCalendar data.
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<iCalendar>"%>BEGIN:VCALENDAR
PRODID:-//jpalm.se//iCalendar example with ASP.NET MVC//EN
VERSION:2.0<%if (Model.Events.Count > 1){%>
X-WR-CALNAME:<%=Model.Name%>
X-WR-CALDESC:<%=Model.Description%>
CALSCALE:GREGORIAN
METHOD:PUBLISH<%}%><%foreach(var evnt in Model.Events){%>
X-MS-OLK-FORCEINSPECTOROPEN:TRUE
BEGIN:VEVENT
DTSTART<%=Model.GetTimeString(evnt.StartTime,evnt.AllDay)%>
DTEND<%=Model.GetTimeString(evnt.EndTime,evnt.AllDay)%>
LOCATION:<%=evnt.Location%>
TRANSP:OPAQUE
SEQUENCE:0
UID:<%=evnt.UID%>
DTSTAMP<%=Model.GetTimeString(evnt.StartTime)%>
CREATED<%=Model.GetTimeString(DateTime.Now)%>
LAST-MODIFIED<%=Model.GetTimeString(DateTime.Now)%>
DESCRIPTION:<%=evnt.Description%>
X-ALT-DESC;FMTTYPE=text/html:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//E
N">\n<HTML>\n<HEAD>\n<TITLE></TITLE>\n</HEAD>\n<BODY><%=evnt.HtmlDescription%></BODY>\n</HTML>
SUMMARY:<%=evnt.Title%><%if(evnt.Reminder){%>
BEGIN:VALARM
TRIGGER:-PT15M
ACTION:DISPLAY
DESCRIPTION:Reminder
PRIORITY:5
END:VALARM<%}%>
END:VEVENT<%}%>
END:VCALENDAR
The X-?? fields are extensions to the iCalendar standard that works with Microsoft Outlook.
Okay, so what do we have here? A strongly typed view based on our iCalendar object. As you can see the inline server tags are not on their own lines. This is because we don’t want any extra line breaks in our output.
Microsoft Outlook behaves a bit differently depending on whether the iCalendar file contains one or several events. If there’s only one event Outlook will prompt the user with a ‘New appointment’-dialog that will be populated with the data from the file. All the user has to do is press save and the event is saved the user’s default calendar. If there are several events in the file Outlook will add a new calendar and all the events are displayed in that. This is why we only set a calendar name and description if the Model.Events.Count is larger then 1.
Other than that we don’t do anything funky. All the fields are defined in the iCalendar format.
So, last step. How do we use this?
public ActionResult Calendar()
{
var icalendar = new iCalendar();
icalendar.Events.Add(new Event
{
StartTime = DateTime.Now.AddHours(1),
EndTime = DateTime.Now.AddHours(2),
Title = "iCalendar blog post",
Description = "Write a blog post about using iCalendar in ASP.NET MVC",
Location= "@ Home"
});
//The file name to display in the Open/Save-file dialog.
string filename = icalendar.Events[0].Title;
//IE want's you to url encode the file name. Other browsers don't like this.
if (Request.UserAgent.ToLower().Contains("msie"))
filename = Server.UrlEncode(filename).Replace("+", " ");
//Set a content type
this.Response.ContentType = "text/calendar";
//Make the browser display the Open/Save-file dialog.
this.Response.AddHeader("Content-disposition", "attachment; filename=\"" + filename + ".ics\"");
//Render the view
return View("iCalendar", icalendar);
}
You probably should wrap the file name and header set up into a new ActionResult based class and then use that instead of setting this directly in the action method.
If the user presses a link that executes the Action above a save file dialog will appear and if Microsoft Outlook is installed the Create Appointment-dialog will display when the file is opened.

And that’s how I added iCalendar support with ASP.NET MVC.
c6ea01b5-0678-4d66-ac04-711f6694d9dd|1|5.0
ASP.NET MVC
asp.net mvc, icalendar