How do I access my Outlook contacts from my web application?
Question
How do I access my Outlook contacts from my web application?
Short Answer
You don't, well at least you shouldn't (It is theoretically possible to access Outlook data from a web application, assuming Outlook is installed and the profile you wish to access data from is setup on the web server).
Long Answer
To address this question we need to realize that Outlook is a client application which is only a front end and local cache for a mail server (typically exchange server). Most web applications are server applications and thus run on servers which usually don't have client applications, such as Outlook, installed on them. Perhaps a better question would be "How do I access my Exchange contacts from my web application?".
With that in mind lets explore a couple possible solutions for accessing data (not really Outlook data) from the Exchange server.
- WebDAV - Protocol that Outlook Web Access (OWA) uses to access your Exchange store
- Exchange Web Services (EWS) - New services added to Exchange 2007 to allow access to most data
- Active Directory Services Interface (ADSI) - For accessing general Exchange user data information (i.e. access the Global Address List)
These are just three common technologies for accessing data on a Exchange server, for more details see the Exchange Server Development Center or Overview of Programming Features for Exchange Server. To provide a more complete example I've included some code samples and links for each of the three technologies. The code examples demonstrate how to do find contacts using each of the three methods. Keep in mind these samples can be extended to other data types such as mail and appointments, with the exception of ADSI. All the code samples are written in C# and write the data to the console, they can easily be converted to be used by an ASP.Net web application. Also remember to substitute <ExchangeServer>, <username>, <password>, and <domain> with the appropriate values for your Exchange server.
WebDAV
The sample retrieves all contacts from an exchange store which have a first name beginning with 'wes'. Keep in mind before making any Exchange WebDAV requests you must make an forms authorization request first (assuming the Exchange server has forms authorization enabled, which seems to generally be the case). You need to make the authorization request to get the set of authorization cookies to use with any subsequent WebDAV requests.
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
// When Exchange is setup for Forms Authentication you need to do the login separately
public static CookieCollection GetAuthCookies(string server, NetworkCredential credentials)
{
// URI to OWA authorization dll
string authURI = string.Format("{0}/exchweb/bin/auth/owaauth.dll",
server, credentials.UserName);
// Get byte stream of the post request
byte[] bytes = Encoding.UTF8.GetBytes(string.Format(
"destination={0}/exchange/{1}&username={2}\\{1}&password={3}",
server, credentials.UserName, credentials.Domain, credentials.Password));
HttpWebRequest request = WebRequest.Create(authURI) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencode";
request.CookieContainer = new CookieContainer();
request.ContentLength = bytes.Length;
request.AllowAutoRedirect = false;
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(bytes, 0, bytes.Length);
// Get response cookies - keep in mind this may throw exceptions
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
return response.Cookies;
}
public static void PrintContactsUsingExchangeWebDAV()
{
string server = "http<s>://<ExchangeServer>";
NetworkCredential credentials = new NetworkCredential("<username>", "<password>", "<domain>");
string uri = string.Format("{0}/exchange/{1}", server, credentials.UserName);
// Create a byte stream of the SQL query to run against the server
// This query searches for contacts with the givenName the begins with 'wes'
// Link to Exchange store property names
byte[] contents = Encoding.UTF8.GetBytes(string.Format(
@"<?xml version=""1.0""?>
<g:searchrequest xmlns:g=""DAV:"">
<g:sql>
SELECT
""urn:schemas:contacts:sn"", ""urn:schemas:contacts:givenName"",
""urn:schemas:contacts:email1"", ""urn:schemas:contacts:telephoneNumber""
FROM
Scope('SHALLOW TRAVERSAL OF ""{0}/exchange/{1}/contacts""')
WHERE
""urn:schemas:contacts:givenName"" LIKE 'wes%'
</g:sql>
</g:searchrequest>",
server, credentials.UserName));
HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
request.Credentials = credentials;
request.Method = "SEARCH";
request.ContentLength = contents.Length;
request.ContentType = "text/xml";
request.CookieContainer = new CookieContainer();
// Keep in mind you may actually want to cache these cookies for other requests
request.CookieContainer.Add(GetAuthCookies(server, credentials));
using (Stream requestStream = request.GetRequestStream())
requestStream.Write(contents, 0, contents.Length);
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
using (Stream responseStream = response.GetResponseStream())
{
// Process the response as an XML document
XmlDocument document = new XmlDocument();
document.Load(responseStream);
foreach (XmlElement element in document.GetElementsByTagName("a:prop"))
{
// Do work with data returned for each contact
Console.WriteLine("Name: {0} {1}\nEmail: {2}\nPhone: {3}",
(element["d:givenName"] != null ? element["d:givenName"].InnerText : ""),
(element["d:sn"] != null ? element["d:sn"].InnerText : ""),
(element["d:email1"] != null ? element["d:email1"].InnerText : ""),
(element["d:telephoneNumber"] != null ? element["d:telephoneNumber"].InnerText : ""));
}
}
}
Helpful WebDAV Links:
Infinitec Exchange posts - C# and JavaScript forms authentication examples
Marc Charbonneau - Using .NET and WebDAV to access an Exchange server
Hitchhiker guide to enterprise development - Programmatically accessing to OWA through Web Dav
Exchange store property names
Exchange Web Services
This sample code goes through the process of creating a Exchange web service FindItem request. In essentially searches through the contact folder looking for any contact with first name starting with 'wes'.
using System;
using System.Net;
// Generate Exchange Web Services proxy classes by adding a VS web
// reference to http<s>://<ExchangeServer>/EWS/Services.wsdl
// then add a using reference to your proxy classes
public static void PrintContactsUsingExchangeWebServices()
{
ExchangeServiceBinding esb = new ExchangeServiceBinding();
esb.Credentials = new NetworkCredential("<username>", "<password>", "<domain>");
esb.Url = @"http<s>://<ExchangServer>/EWS/Exchange.asmx";
// Tell it you want all the item properties
ItemResponseShapeType itemProperties = new ItemResponseShapeType();
itemProperties.BaseShape = DefaultShapeNamesType.AllProperties;
// Tell it you only want to look in the contacts folder
DistinguishedFolderIdType[] folderIDArray = new DistinguishedFolderIdType[1];
folderIDArray[0] = new DistinguishedFolderIdType();
folderIDArray[0].Id = DistinguishedFolderIdNameType.contacts;
PathToUnindexedFieldType field = new PathToUnindexedFieldType();
field.FieldURI = UnindexedFieldURIType.contactsGivenName;
ConstantValueType fieldValue = new ConstantValueType();
fieldValue.Value = "wes";
// Look for contacts which have a given name that begins with 'wes'
ContainsExpressionType expr = new ContainsExpressionType();
expr.ContainmentModeSpecified = true;
expr.ContainmentMode = ContainmentModeType.Prefixed;
expr.ContainmentComparisonSpecified = true;
expr.ContainmentComparison = ContainmentComparisonType.IgnoreCase;
expr.Constant = fieldValue;
expr.Item = field;
RestrictionType restriction = new RestrictionType();
restriction.Item = expr;
// Form the FindItem request
FindItemType findItemRequest = new FindItemType();
findItemRequest.Traversal = ItemQueryTraversalType.Shallow;
findItemRequest.ItemShape = itemProperties;
findItemRequest.ParentFolderIds = folderIDArray;
findItemRequest.Restriction = restriction;
// Send the request and get the response
FindItemResponseType findItemResponse = esb.FindItem(findItemRequest);
if (findItemResponse.ResponseMessages.Items.Length > 0)
{
FindItemResponseMessageType responseMessage =
findItemResponse.ResponseMessages.Items[0] as FindItemResponseMessageType;
ArrayOfRealItemsType realItems =
responseMessage.RootFolder.Item as ArrayOfRealItemsType;
foreach (ContactItemType contact in realItems.Items)
{
// Do work with data returned for each contact
Console.WriteLine("Name: {0} {1}\nEmail: {2}\nPhone: {3}",
contact.GivenName, contact.Surname,
(contact.EmailAddresses != null && contact.EmailAddresses.Length > 0 ?
contact.EmailAddresses[0].Value : ""),
(contact.PhoneNumbers != null && contact.PhoneNumbers.Length > 0 ?
contact.PhoneNumbers[0].Value : ""));
}
}
}
Useful Exchange Web Service Links:
MSDN Exchange Web Services Reference
Stephen Griffin - Exchange Web Services and MAPI Props
Eric Lee - Exchange Server 2007 Web Service API for Developers
Active Directory Services Interface
This is a very simplified .Net sample of doing a search in the default active directory for any users that have a first name beginning with 'wes'. Keep in mind that this uses the default active directory on the machine you are running on, see DirectorySearcher.SearchRoot for more details. If you need to specify a particular machine/directory to start your search from then you need to create a DirectoryEntry and pass that to the DirectorySearcher.
using System;
using System.DirectoryServices;
// Get user information from your default active directory repository
public static void PrintUsersFromADSI()
{
// Find any user that has a name that begins with 'wes'
string filter = "(&(objectCategory=person)(objectClass=user)(givenName=wes*))";
DirectorySearcher search = new DirectorySearcher(filter);
foreach(SearchResult result in search.FindAll())
{
// Do work with data returned for each address entry
DirectoryEntry entry = result.GetDirectoryEntry();
Console.WriteLine("Name: {0} {1}\nEmail: {2}\nPhone: {3}",
entry.Properties["givenName"].Value,
entry.Properties["sn"].Value,
entry.Properties["mail"].Value,
entry.Properties["telephonenumber"].Value);
}
}
Useful ADSI/LDAP Links:
InfiniTec - How to get the Global Address List programmatically
Amit Zinman - Creating a list of Users and their e-mail addresses
Steve Schofield - .NET sample LDAP query looking for a specific user name of (smith) and System.DirectoryServices namespace
Mow - PowerShell and Active Directory Part 1, Part 2, Part 3, Part 4, Part 5
Disclaimer: The sample code described here is provided on an “as is” basis, without warranty of any kind, to the fullest extent permitted by law. It is for demonstration purposes only and is by no means production quality.