Vous êtes sur la page 1sur 7

Calendar Information from MS Exchange using C# - Part 1

by Leslie Drewery Background


I have two words that strike fear into any exchange administrator: Team Calendars. Our original approach was that, every Friday, our receptionist would go through all our Outlook calendars, consolidate them into an Excel spreadsheet, then email them to our remote sites. Crude but effective. As I was not involved, I thought this was a great idea until we started to grow. The easy 20-minute job had now become a two-hour ordeal leading to hours of eyelash batting, begging and blatant threats. (Personally, I thought if they stopped the threats they could have used the time constructively to enter the details into the spreadsheet.) After having been shunned, I decided to do a little homework and came up with the following solution: Extract calendar details from Exchange in real-time and display them on our web site, therefore leaving our receptionist time to shower me with praise (well, at least end the banishment and allow me back onto the birthday card list).

Definitions
WebDAV stands for Web-based Distributed Authoring and Versioning. It is a set of extensions to the HTTP protocol which allows users collaboratively to edit and manage files or datastores on remote web servers. OWA stands for Outlook Web Access.

Overview
I have broken the process into two articles, this first one being about extracting the data into a usable format. I will follow this up with how to use the data in a team calendar web page. This article demonstrates how to extract calendar events from Microsoft Exchange using Outlook Web Access (OWA) into a .NET Dataset. The following steps are taken to retrieve the data: 1. 2. 3. Build WebDav Query. Parse returned DOM and build Dataset. Use dataset in any way you see fit.

Requirements
MS Exchange 2003 with Web Mail installed. Please Note: Authentication method must be known. A user setup with permissions to review the personal calendars in MS Exchange. The actual mail box names for the members to be displayed (this is very important, often the NT logon name). Visual Studio 2003 and working knowledge of XML and SQL. And finally patience and a strong will to live.

The Developers Group Magazine November/December 2005

Page 1

return to index

Process Flow

The Developers Group Magazine November/December 2005

Page 2

return to index

Building WebDAV Query


OWA uses WebDAV as its preferred communication method. There are many resources on the web on how to build these requests, or you can make friends with an Outlook VBA programmer.
#region Build WebDav Query // Build the SQL query. StringBuilder strQuery = new StringBuilder(); strQuery.Append(<?xml version=\1.0\?>); strQuery.Append(<g:searchrequest xmlns:g=\DAV:\>); strQuery.Append(<g:sql>SELECT ); //Location of the Calendar Event. strQuery.Append(\urn:schemas:calendar:location\ ); //Title of the Event. strQuery.Append(, \urn:schemas:httpmail:subject\ ); //Notes about the Event, this only returns the basic text of the Note. strQuery.Append(,\urn:schemas:httpmail:textdescription\ ); //Start Date of the Event. strQuery.Append(,\urn:schemas:calendar:dtstart\ ); //End Date of the Event. strQuery.Append(,\urn:schemas:calendar:dtend\); //Show Time as OOF (Out of Office), Free, Busy, Tentative. strQuery.Append(,\urn:schemas:calendar:busystatus\ ); //Instance Type. strQuery.Append(,\urn:schemas:calendar:instancetype\ ); //Indicates if the event is private or public. strQuery.Append(,\urn:schemas:mailheader:sensitivity\ ); //Checks if the event is an all day event. strQuery.Append(,\urn:schemas:calendar:alldayevent\ ); //Entry ID strQuery.Append(,\http://schemas.microsoft.com/mapi/proptag/x0fff0102\ ); strQuery.Append(FROM Scope(SHALLOW TRAVERSAL OF \ + serverPath + \) ); strQuery.Append(WHERE NOT \urn:schemas:calendar:instancetype\ = 1 ); strQuery.Append(AND \DAV:contentclass\ = urn:content-classes:appointment ); strQuery.Append(AND \urn:schemas:calendar:dtstart\ &gt; + startDate.AddDays(1).ToString(yyyy/MM/dd) + 00:00:00' ); strQuery.Append(ORDER BY \urn:schemas:calendar:dtstart\ ASC); strQuery.Append(</g:sql></g:searchrequest>); #endregion

Handling Secure Server Calls


When the OWA is held on a secure IIS server, we need to cater for certificate handling; in this example we accept any certificate without validation. By assigning the AcceptAllCertificatePolicy class to the ServicePointManager.CertificatePolicy the CheckValidationResult method will be only executed when a certificate is received, in this example we return true to indicate the certificate is valid.
ServicePointManager.CertificatePolicy = new AcceptAllCertificatePolicy(); #region Certificate Handling Class /// <summary> /// Certificate Handler /// </summary> internal class AcceptAllCertificatePolicy : ICertificatePolicy { public AcceptAllCertificatePolicy() { } /// <summary> /// Certificate Handling should be done here. /// /// Currently all Certificates will be accepted. /// </summary> /// <param name=sPoint></param> /// <param name=cert></param> /// <param name=wRequest></param> /// <param name=certProb></param> /// <returns></returns> public bool CheckValidationResult(ServicePoint sPoint, X509Certificate cert, WebRequest wRequest,int certProb) { return true; } } #endregion

The Developers Group Magazine November/December 2005

Page 3

return to index

Handling Server Credentials


The user credentials need to have access to view the users personal calendar supplied in the serverPath. When NTLM authorization is used on the OWA a network credentials object must be passed with every call.
#region Handle Secure Server Calls // Create a new CredentialCache object and fill it with the network // credentials required to access the server. System.Net.CredentialCache MyCredentialCache = new System.Net.CredentialCache(); MyCredentialCache.Add(new System.Uri(serverPath),NTLM, new System.Net.NetworkCredential(userName, userPassword, userDomain)); #endregion

When dealing with forms based authentication, a session cookie must be received from the exchange server and passed with every call made to the server. I have attached the following method to retrieve the authentication cookies.
public CookieCollection ExchangeFormBasedAuthenticationCookies (string uri1,NetworkCredential credential) { // Get the server portion of the requested uri and append the authentication dll from Exchange string server = uri.Substring(0, uri.IndexOf(/, 8)) + /exchweb/bin/auth/owaauth.dll; HttpWebRequest request = (System.Net.HttpWebRequest)HttpWebRequest.Create(server); request.Method = POST; //Create cookie container to retrieve return cookiecollection request.CookieContainer = new CookieContainer(); request.ContentType = application/x-www-form-urlencoded; byte[] body = Encoding.UTF8.GetBytes(string.Format(destination={0}&username={1}\\{2}&password={3},server + /exchange/ + credential.UserName +/Calendar, credential.Domain, credential.UserName, credential.Password)); request.ContentLength = body.Length; Stream stream = request.GetRequestStream(); stream.Write(body, 0, body.Length); stream.Close(); // Get the response httpWebResponse response = (HttpWebResponse) request.GetResponse(); // Check if the login was successful if (response.Cookies.Count < 2) throw new Exception(Login failed!!); //Return Cookie collection return response.Cookies; }

1.

The serverPath is the path to the exchange calendar container (https://<hostName>/exchange/<mailboxID>/calendar)


// Create the HttpWebRequest object. System.Net.HttpWebRequest Request = (System.Net.HttpWebRequest)HttpWebRequest.Create(serverPath);

2.

Add the NTLM network credentials to the request.


//Add NTLM Authentication to to the Request. Request.Credentials = MyCredentialCache;

Please Note: This will not work on a Forms Authenticated Exchange server: you will get an unintuitive response of Login Timeout. When using forms authentication replace point 2 with the following.
//Create Cookie Collection container Request.CookieContainer = new CookieContainer(); //Call Exchange Authentication DLL and return Authentication Cookies (There must be at least 2 cookies or authentication failed.) CookieCollection cookiecollection = ExchangeFormBasedAuthenticationCookies(serverPath,new System.Net.NetworkCredential(strUserName, strPassword,strDomain)); //Assign Authentiacation Cookie to Every call made to the Exchange server Request.CookieContainer.Add(cookiecollection);

The Developers Group Magazine November/December 2005

Page 4

return to index

3.

This specifies we want to search the exchange server folder.


// Specify the method. Request.Method = SEARCH;

4.

Encode query to be passed to WebDav Request.


// Encode the body using UTF-8. byte[] bytes = Encoding.UTF8.GetBytes(strQuery.ToString());

5.

Specify the number of bytes to transmit.


// Set the content header length. This must be // done before writing data to the request stream. Request.ContentLength = bytes.Length;

6.
// Get a reference to the request stream. System.IO.Stream RequestStream = Request.GetRequestStream();

7.
// Write the SQL query to the request stream. RequestStream.Write(bytes, 0, bytes.Length);

8.
// Close the Stream object to release the connection // for further use. RequestStream.Close();

9.

We then assign request content type as XML.


// Set the content type header. Request.ContentType = text/xml;

Handling the call to the MS Exchange Server.


All thats left to do is to pass the request to the Exchange server and parse the resulting XML document.
// Send the SEARCH method request and get the // response from the server. System.Net.WebResponse Response = (HttpWebResponse)Request.GetResponse(); // Get the XML response stream. System.IO.Stream ResponseStream = Response.GetResponseStream(); // Create the XmlDocument object from the XML response stream. System.Xml.XmlDocument ResponseXmlDoc = new XmlDocument(); //Load The XML Document into the XMLDocument ResponseXmlDoc.Load(ResponseStream);

Return DataTable
In the example we build a data table on the fly to return the data.

Table structure
Field name Entry ID Subject Location Busy AllDayEvent Sensitivity InstanceType Href StartTime EndTime Data Type Description String String String String Boolean String String String DateTime DateTime Unique identifier assigned be MS exchange server for the calendar event. Subject line of the calendar event. Location of the calendar event. OOF, Busy, Free, Tentative Status for the calendar event. Mark the event as an ALL Day Event. Indicates Level of Sensitivity (Private indicates private check box was ticked, blank makes it a public event) Instance type. This is the URL to the item on the on the IIS server. Start date for the calendar event. End date for the calendar event.

The Developers Group Magazine November/December 2005

Page 5

return to index

Populate Return DataTable.


The data stream returned from the MS Exchange server is in XML format. The complete document is put into a XMLDocument then each field is extracted into a XMLNode using the TagName. The thing to notice is that each XMLNode should have the same number of XML items. If the field is missing in the document returned from the MS Exchange server a blank item is inserted into the XMLNode.
#region XMLNode Lists Management //g:x0fff0102 (Entry ID) is the an Unique ID per Exchange Item. System.Xml.XmlNodeList EntryIDNodeList = responseXmlDoc.GetElementsByTagName(g:x0fff0102); //e:subject is the Subject for the calendar event. System.Xml.XmlNodeList SubjectNodeList = responseXmlDoc.GetElementsByTagName(e:subject); //e:textdescription is the Note for the calendar event. System.Xml.XmlNodeList NotesNodeList = responseXmlDoc.GetElementsByTagName(e:textdescription); //d:location is the Location Field for the calendar event. System.Xml.XmlNodeList LocationNodeList = responseXmlDoc.GetElementsByTagName(d:location); //d:busystatus is the Time Category.OOF- Out of Office, BUSY, FREE System.Xml.XmlNodeList BusyStatusNodeList = responseXmlDoc.GetElementsByTagName(d:busystatus); //f:sensitivity is the visibility for the calendar item. //PRIVATE - This highlights the private flag was set. System.Xml.XmlNodeList SensitivityNodeList = responseXmlDoc.GetElementsByTagName(f:sensitivity); //d:instancetype is the Instance Type. System.Xml.XmlNodeList InstanceTypeNodeList = responseXmlDoc.GetElementsByTagName(d:instancetype); //href is the physical server path to the calendar event item. System.Xml.XmlNodeList hrefNodeList = responseXmlDoc.GetElementsByTagName(a:href); //dtstart is the Start Date for the calendar event. System.Xml.XmlNodeList StartTimeNodeList = responseXmlDoc.GetElementsByTagName(d:dtstart); //dtend is the Start Date for the calendar event. System.Xml.XmlNodeList EndTimeNodeList = responseXmlDoc.GetElementsByTagName(d:dtend); //alldayevent is the All day event flag for calendar event. // NOTE : when this flag is set the time for both start and end will be set 00:00. System.Xml.XmlNodeList AlldayEventNodeList = responseXmlDoc.GetElementsByTagName(d:alldayevent); #endregion

We then loop through all the XMLNodes and insert each Item into the DataTable.
#region Build return dataset. //Build Return Table. DataTable calendarEventsTable = BuildCalendarTable(tableName); for(int i=0; i<SubjectNodeList.Count; i++) { DataRow calendarEventRow = calendarEventsTable.NewRow(); calendarEventRow[EntryID] = EntryIDNodeList[i].InnerText; calendarEventRow[Subject] = SubjectNodeList[i].InnerText; calendarEventRow[Notes] = NotesNodeList[i].InnerText.ToString(); calendarEventRow[Location] = LocationNodeList[i].InnerText; calendarEventRow[Busy] = BusyStatusNodeList[i].InnerText; if (AlldayEventNodeList[i].InnerText == 1) calendarEventRow[AllDayEvent] = true; else calendarEventRow[AllDayEvent] = false; calendarEventRow[Sensitivity] calendarEventRow[InstanceType] calendarEventRow[href] = SensitivityNodeList[i].InnerText; = InstanceTypeNodeList[i].InnerText; = hrefNodeList[i].InnerText;

The Developers Group Magazine November/December 2005

Page 6

return to index

calendarEventRow[StartTime]= Convert.ToDateTime(StartTimeNodeList[i].InnerText); calendarEventRow[EndTime] = Convert.ToDateTime(EndTimeNodeList[i].InnerText); calendarEventsTable.Rows.Add(calendarEventRow); } #endregion

The resulting DataSet can be consumed in any .NET application using DataBinding. I hope this article gives you a better idea on how to create a team calendar without much effort or time. Leslie Drewery GG (general gopher) is one of the developers for CompuFile Limited. He develops in Delphi and C# covering Win32 through to ASP.net and recently DirectShow using DirectX and not much of an author. [Im not complaining! Ed] He also has a keen interest in PC based Digital TV systems using .net. He can be reached via his website http://www.firedtv.com or email leslie@firectv.com [Thanks to Brian Long for tech editing and commenting on this article. Any remaining anomalies are mine. Ed]

Thomas Muellers Pseudo Templates


If you followed Thomass article in our last issue, you may be interested in the project on his wiki which includes a few container templates using the technique described in the article. See http://openfacts.berlios.de/index-en.phtml?title=DzTemplates_-_Container_pseudo_templates

About dzTemplates
dzTemplates has grown out of the article linked above. It is a set of type safe containers that can be easily used to store whatever data you want it to store.

Available containers
The following containers are available:

List templates are the most generic containers. They come in two flavours: One that stores anything
that can be typecasted into a pointer or an interface and one that assumes the items are descendants of TObject and "owns" them (that is, it will free them in its destructor). There is also an interface declaration for this kind of container (dzListInterfaceTemplate).

dzListTemplate / dzObjectListTemplate is the most generic container it stores its items in the
order they are added.

dzSortedListTemplate / dzSortedObjectListTemplate is based on dzListTemplate adds a sort


order to it. It introduces methods for comparing two items and searching for them with a binary search algorithm. A Duplicates property like the one in TStringList determines what to do with duplicate items.

dzIntegerSortedListTemplate / dzIntegerSortedObjectListTemplate is a specialized descendant of dzSortedListTemplate which assumes that its items are sorted by an integer value.

dzStackTemplate / dzObjectStackTemplate are both stacks (LIFO = last in first out), again one for
storing anything that can be typecasted to a pointer or interface and one for storing TObject descendants.

dzQueueTemplate is a queue (FIFO = first in first out) for storing anything that can be typecasted to a
pointer or interface. There currently is not dzObjectQueueTemplate but I will add one shortly.

dzHashTemplate is a hash or associative array for storing items and accessing them via an associated
string.

The Developers Group Magazine November/December 2005

Page 7

return to index

Vous aimerez peut-être aussi