Exposing SharePoint calendars for iCalendar clients

Hello there,

since Sharepoint offers out-of-the-box calendar and scheduling capabilities, it is a nice option for teams in terms of improved collaboration and sharing common agendas. By default, users can access the MOSS calendars using a browser or by syncing it with their Microsoft Outlook clients. Additionally, a RSS subscription is also available.
So far, so good, but if we need to subscribe to the calendar using a Mac or an iPhone, iPad, or any other client supporting the iCalendar specification, we have to use a custom solution.

Simply put, a file with the content type text/calendar needs to be generated, and it has to more or less comply with the RFC 2445 specification. Of course you may want to implement it your way from scratch, but there’s a nice iCal Exporter kit from CodePlex. It has been developed as a MOSS feature, but you can easily adapt it to serve users in a different way, for example as a custom http handler. So one can create a class inheriting the IHttpHandler interface, deploy the class library onto the Sharepoint application, and register the handler in web.config:

<add verb="GET,POST" path="*/ical.ics" type="MyLibrary.MyIcalHandler" />

Then we could access the generated file for example via http://myhost/mysite/lists/calendar/ical.ics , and subscribe to the calendar in a corresponding client software. In the handler itself, based on the request URL, we can fetch the corresponding Sharepoint list, iterate through its items and generate the proper entries, then flush it to the browser. The iCal Exporter kit also deals with recurring calendar entries, deleted event series’ occurences, all-day events, so that almost every case is covered.

One thing worth mentioning: when generating an event entry, be careful using the DTSTAMP property. In order for iCal clients to properly recognize changes made to calendar entries, one has to assign the last-modified-date property of the corresponding SPListItem as DTSTAMP value:

 foreach (SPListItem item in calendarList.Items)
 {
    // write vevent start...
    DateTime modified = Convert.ToDateTime(item["Modified"]);
    String dtstamp = "DTSTAMP:" + modified.ToString("yyyyMMddTHHmmssZ");
    // write dtstamp, other properties, vevent end
 }

Otherwise you may encounter a problem that the client won’t fetch a calendar entry’s changes.

Hope this helps,
Łukasz

Verbs configuration for custom HTTP handlers

Hello,

if your ASP.NET or Sharepoint web application is storing binary data (images, PDF’s) in a database, you’ll probably need a HTTP handler which would retrieve those files from DB, and then transfer them to the user’s browsers as well as set the correct content type of the response. A clever way to do that is using the IHttpHandler interface, but that’s not the point of this entry.

Assuming that we have our handler ready and working, for example, reading jpegs from the DB and presenting them to the public as if it were normal files on the server (www.example.com/name1.jpg). In order for our Sharepoint Web Application to map such request onto a correct handler, we need a web.config entry within the <httpHandlers> section, like this:

<add verb="GET" path="/*.jpg" validate="false" 
type="MyAssembly.MyHandler, MyAssembly, 
Version=1.0.0.0, Culture=neutral, PublicKeyToken=abcdefghi"
/>

The verb attribute defines allowed HTTP request methods for this handler. In the case above, a HTTP POST request would be rejected by the server with a HTTP 404 code. For “normal” purposes, such as displaying the images in browsers, the GET method would be enough.

However, once you know that other clients than web browsers would access the handler, you must know which HTTP methods they’re using. Recently, thanks to Fiddler, I was able to discover that one of the client-apps used in our organization is using HEAD method (very similar to GET) in order to retrieve a file. Since the method was not enabled in our configuration, the client could not download the desired file.

Enabling the HEAD method within the handler’s verbs in web.config solves the issue:

<add verb="GET,HEAD" path="/*.jpg" (...) />

Hope this helps,
Łukasz