Programming Microsoft Outlook and Microsoft Exchange, Second Edition (DV-MPS Programming)
Chapter 19
Putting It All Together
Using XML with Exchange Server
One of the biggest buzzwords in the industry has been XML. XML provides an easy way to describe data and share it among applications. Exchange Server 2000 directly supports XML, making it a great Web development platform. In other words, you can use XML to get and set data directly in Exchange Server.
I couldn't possibly attempt to cover all aspects of XML in this chapter. However, many good books on the topic exist. Instead, I'll discuss how to get, set, search, and format using XSL, XML data in Exchange Server 2000. Since you'll just be querying for data, I won't cover how to create an XML document from scratch.
XMLHTTP Component
You can retrieve XML data from Exchange 2000 in a number of different ways. The technique you'll probably use the most is to employ the Web Distributed Authoring and Versioning (WebDAV) protocol. WebDAV is an extension to the http protocol that specifies how to perform file processing, thus making the Web extremely readable and writable. Using WebDAV commands, you can lock a resource, get a property, or change a property. This is a powerful capability—WebDAV can work through firewalls and proxy servers because it piggybacks on http.
You might be wondering how to use to a protocol such as WebDAV within your Web applications. Internet Explorer 5 ships with a component called XMLHTTP, to work with the WebDAV protocol. You will still have to send correctly formatted WebDAV requests, but the XMLHTTP component simplifies this. Be aware that you should use the XMLHTTP component only on the client side since the component was built for that environment. If you're writing code on the Exchange server, you should write to OLE DB or ADO.
We'll take a look at the most important properties and methods of the XMLHTTP component so that you can use them in your applications. These properties and methods are Abort, OnReadyStateChange, Open, ReadyState, responseBody, responseStream, responseText, responseXML, Send, SetRequestHeader, Status, and StatusText. All other properties and methods of XMLHTTP are fully documented on the Microsoft Developer's Network (MSDN), http://msdn.microsoft.com.
Open Method
The first method that you'll probably use with the XMLHTTP component is the Open method. This method takes five parameters: Method, URL, Async, User, and Password. The Method parameter specifies which http method to use: GET, POST, PROPPATH, SEARCH, or another method. The URL parameter is the absolute URL to the resource—for example, http://myserver/documents. The Async parameter is a Boolean that specifies whether the call is asynchronous. If set to True, which is the default, the call returns immediately. Finally, you can use the User and Password parameters to pass the username and password with which you want the component to access secured sites. The following code example, taken from the coursexml.asp file in the Training application, shows you how to create an XMLHTTP object and use the Open method:
request = new ActiveXObject("microsoft.xmlhttp"); //Example propfind //request.open("PROPFIND", URLSchedule, true); request.open("SEARCH", URLSchedule, true); |
SetRequestHeader Method
The SetRequestHeader method allows you to specify the name and value for an http header. The most common headers that you will set are depth and content-type. A depth header specifies how deep in a hierarchy the request applies. For example, setting the depth header to <;$QD>1, noroot<;$QD> means that the method will be applied to all the children of the specified URL, but not to the URL itself. If you do not specify a depth, Exchange will default to an infinite depth. A content-type header specifies the content type you plan to send to the server. The following code snippet shows you how to use this method:
//For propfind you can select a depth //request.setRequestHeader("depth", "1,noroot"); request.setRequestHeader("Content-type", "text/xml"); |
ReadyState Property
The ReadyState property contains the state of the request object. This property can contain one of the five values shown in Table 19-1.
Table 19-1. Values of the ReadyState property.
Value | Description |
---|---|
UNITIALIZED | The object has been created, but the Open method has not been called. |
LOADING | The object has been created, but the Send method has not been called. |
LOADED | The Send method has been called, but the response is not yet available. |
INTERACTIVE | The object is not fully loaded yet, but the user can already interact with it. Partial results can be viewed in the browser. |
COMPLETED | All data has been received and can be viewed in the browser. |
OnReadyStateChange Property
This property allows you to specify which event handler to call when the ReadyState property changes. The following code sets the function to be called to dostatechange. We'll see what dostatechange does in a moment, when we look at the responseXML property.
request.onreadystatechange = dostatechange; |
Send Method
As you would guess, this method sends the request to the server. You specify the request as a string parameter to this method. The following code sends the body of our request to the server. We'll see what this body looks like later in the chapter, when we examine WebDAV requests.
request.send(body); |
responseBody, responseStream, responseText, and responseXML Properties
All of these properties allow you to retrieve the response in each format specified. You'll most commonly use responseText and responseXML in your applications. The responseText property returns the XML response as a text string. The responseXML property returns the response as an XML document parsed by the XML Document Object Model (XMLDOM), meaning you'll receive an object that you can format using XSL or you can call methods on the XMLDOM to interact with the data. You'll use these properties in the event handler you specified for the onreadystatechange property. The following code gets the XML response and sends it to be formatted by XSL:
thexml = request.responseXML; datediv.innerHTML = "<B>Showing Courses from <%=dDateStart%> to <%=dDateEnd%></B><BR>" //Check for empty body if (thexml.selectSingleNode("a:multistatus/a:response") == null) { msgdiv.innerHTML = "<B>No courses found.</B>"; request = null; return; } //For debugging purposes //alert(request.responseText); msgdiv.innerHTML = thexml.transformNode(reportXSL.documentElement); |
Status and StatusText Properties
Both of these properties contain status information about the request. Status contains the http status returned by the request. This is an integer that corresponds to one of the http status codes. StatusText returns a string that represents the status returned by the request. The following code checks to see whether the Status property has a value of 207, which indicates that the request was successful. If Status does not have a value of 207, the code prints out the values of the Status and StatusText properties as an error.
if(request.status != 207) { msgdiv.innerText = "Error, status = " + request.status + " " + request.statusText; msgdiv.style.fontFamily = "verdana" |
Abort Method
The Abort method will cancel the current http request and restore the XMLHTTP component to the UNINITIALIZED state.
WebDAV Commands
Now that you know about the object model of XMLHTTP, which allows you to send commands to the Exchange server, you're probably wondering what these commands are. WebDAV supports a number of commands, including MKCOL, PROPPATCH, PROPFIND, DELETE, MOVE, COPY, SEARCH, LOCK, UNLOCK, SUBSCRIBE, and POLL. Each of these commands serves a distinctive purpose in your applications. For example, MKCOL allows you to create a collection (or a folder) on your server. PROPPATCH and PROPFIND allow you to set and get properties on resources. Let's look at some typical tasks you'll perform with WebDAV so that you can see each of these commands in action.
Creating Folders
To create a folder using WebDAV, you need to issue the MKCOL command and pass the URL of the new folder that you want to create. If the creation is successful, you'll receive the Status of 201 and the StatusText of Created. The following code uses XMLHTTP to create a new folder:
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder"; var request = new ActiveXObject("Microsoft.XMLHTTP"); request.open("MKCOL",strURL,false); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Creating Items
To create an item, you'll use the http PUT method and pass to it the URL of the new item that you want to create. If successful, you'll get the Status of 201 and the StatusText of Created. The following code creates a new post in the Public Folder we created earlier:
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder/my new item.eml/"; var request = new ActiveXObject("Microsoft.XMLHTTP"); request.open("PUT",strURL,false); request.setRequestHeader("Translate","f"); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Copying Folders and Items
To copy both items and folders, use the COPY command. This command takes as a parameter the source URL of the copied item. As part of your request headers, you need to specify the destination URL for the copied item. If you are copying a folder, all the contents of that folder will be copied as well. The following code copies the newly created folder and item from the earlier example into another folder. If successful, you should receive the Status value of 201.
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strSourceURL = "http://localhost/public/my new folder/"; var strDestURL = "http://localhost/public/my new folder 2/"; var request = new ActiveXObject("Microsoft.XMLHTTP"); request.open("COPY",strSourceURL,false); request.setRequestHeader("Destination",strDestURL); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Moving Folders and Items
Moving folders is as easy as copying them. All you need to do is change the command from COPY to MOVE. The following code shows you how to move a folder:
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strSourceURL = "http://localhost/public/my new folder/"; var strDestURL = "http://localhost/public/my new folder 3/"; var request = new ActiveXObject("Microsoft.XMLHTTP"); request.open("MOVE",strSourceURL,false); request.setRequestHeader("Destination",strDestURL); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Deleting Items and Folders
To delete items or folders, you use the DELETE command and pass to it the URL of the item to be deleted. If successful, you'll receive the Status of 200 and a StatusText of OK. The following code deletes a folder:
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder 3/"; var request = new ActiveXObject("Microsoft.XMLHTTP"); request.open("DELETE",strURL,false); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Setting Properties
To set properties, you'll need to use the PROPPATCH command. To use this command, you must create an XML document to send that will list the properties you want updated. You can generate this XML document in a number of ways. The two easiest ways are to use the XMLDOM, or to simply generate the XML yourself by using JavaScript code. The following example generates the XML directly and sends it to the server to update the properties:
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder2/my new item.eml/"; request = new ActiveXObject("microsoft.xmlhttp"); request.open("PROPPATCH", strURL, false); request.setRequestHeader("Content-type", "text/xml"); proplist = "<M:subject>My New Message</M:subject>"; proplist = "<M:textdescription>This is my body</M:textdescription>"; body = "<?xml version='1.0'?>"; body += "<D:propertyupdate xmlns:D='DAV:' xmlns:M='urn:schemas:httpmail'>"; body += "<D:set><D:prop>"; body += proplist; body += "</D:prop></D:set>"; body += "</D:propertyupdate>"; request.send(body); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Retrieving Properties
To retrieve properties, you need to use the PROPFIND command. PROPFIND can request a single property, all properties, or all property names. When working with PROPFIND, you need to set the depth header value to the depth you want the request's scope to be. The value for depth can be 0, which indicates only the entity in the URL specified; 1, which signified the target URL and any of its immediate children; or Infinity, which indicates the target URL, its children, and its children's children, all the way down to the leaves of the tree. Also, you need to send an XML document that specifies the property list you want to retrieve from the resource. The following code requests some DAV properties and Microsoft Office properties from a Microsoft Word document in a Public Folder:
<HTML> <BODY> <TEXTAREA rows=10 cols=20 id=textarea1 name=textarea1> </TEXTAREA> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder/my word doc.doc/"; request = new ActiveXObject("microsoft.xmlhttp"); request.open("PROPFIND", strURL, false); request.setRequestHeader("depth", "1,noroot"); request.setRequestHeader("Content-type", "text/xml"); proplist = "<D:iscollection/><D:displayname/><D:getlastmodified/> <D:creationdate/><O:Author/><O:Manager/><O:Title/>"; body = "<?xml version='1.0'?>"; body += "<D:propfind xmlns:D='DAV:' xmlns:O='urn:schemas-microsoft-com:office:office'>"; body += "<D:prop>"; body += proplist; body += "</D:prop>"; body += "</D:propfind>"; request.send(body); alert(request.status + " " + request.statustext); textarea1.value = request.responseText; //--> </SCRIPT> </BODY> </HTML> |
The returned XML stream that is placed into the TEXTAREA follows. Notice that it is a multistatus response.
<?xml version="1.0"?><a:multistatus xmlns:b="urn:uuid:c2f41010-65b3- 11d1-a29f-00aa00c14882/" xmlns:c="xml:" xmlns:d="urn:schemas-microsoft- com:office:office" xmlns:a="DAV:"><a:response><a:href>http://localhost/ public/my%20new%20folder/my%20word%20doc.doc</a:href><a:propstat> <a:status>HTTP/1.1 200 OK</a:status><a:prop><a:iscollection b:dt="boolean"> 0</a:iscollection><a:displayname>my word doc.doc</a:displayname> <a:getlastmodified b:dt="dateTime.tz">2000-03-09T10:33:04.532Z </a:getlastmodified><a:creationdate b:dt="dateTime.tz">2000-03- 09T10:33:04.372Z</a:creationdate><d:Author>Thomas Rizzo</d:Author> <d:Manager>Some Manager</d:Manager><d:Title>This is my word doc</d:Title></a:prop></a:propstat></a:response></a:multistatus> |
To retrieve all properties in the DAV namespace, you would issue the following WebDAV command:
<?xml version="1.0" ?> <D:propfind xmlns:D="DAV:"> <D:allprop/> </D:propfind> |
Locking a Resource
You might want to lock a resource, such as a file or collection, so that no other WebDAV requests can access it. You can acquire an exclusive or shared lock on a resource . Each lock has a timeout, affording you a precise window of opportunity to make your changes before the lock expires. If a lock does expire, you can always request another lock on the resource from the server. The following code requests an exclusive write lock on a Word document in a Public Folder. As you'll see in the code, the lock remains in effect for 3,000 seconds, and the XML body holds an Owner property. If another application tries to modify the Word document, it will receive an XML document that contains a lockdiscovery property, which contains the Owner property of the person who currently has a lock on the resource. The application can use this property to request that the current lock owner disable the lock or notify the application when the lock is released.
<HTML> <BODY> <TEXTAREA rows=10 cols=80 id=textarea1 name=textarea1> </TEXTAREA> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder/my word doc.doc/"; request = new ActiveXObject("microsoft.xmlhttp"); request.open("LOCK", strURL, false); request.setRequestHeader("Content-type", "text/xml"); request.setRequestHeader("timeout", "Second-3000"); body = "<?xml version='1.0'?>"; body += " <a:lockinfo xmlns:a='DAV:'>" body += "<a:lockscope><a:exclusive /></a:lockscope>"; body += "<a:locktype><a:write /></a:locktype>"; body += "<a:owner><a:href>mailto:thomriz</a:href>"; body += "</a:owner></a:lockinfo>"; request.send(body); alert(request.status + " " + request.statustext); textarea1.value = request.responseText; //--> </SCRIPT> </BODY> </HTML> |
After this request is sent to the server, the following response will be received:
<?xml version="1.0"?><a:prop xmlns:a="DAV:"><a:lockdiscovery> <a:activelock><a:locktype><a:write/></a:locktype><a:lockscope> <a:exclusive/></a:lockscope><a:owner xmlns:a="DAV:"><a:href> mailto:thomriz</a:href></a:owner><a:locktoken><a:href> opaquelocktoken:9641CB50-729A-4966-B904-6F55773AA5B7: 10654405112102912001</a:href></a:locktoken><a:depth>infinity </a:depth><a:timeout>Second-3000</a:timeout></a:activelock> </a:lockdiscovery></a:prop> |
If the lock is successful, you'll receive the Status of 200 OK, along with the XML just shown. The most important property to be aware of is the locktoken property. The locktoken property uniquely identifies your lock and must be used in future requests. Because http is stateless, you need to pass the locktoken with your future requests so that the server knows who is attempting to write to the resource. You'll also need this property for the PUT, PROPPATCH, and other requests you send, and to unlock the file.
NOTE
If the resource is already locked when you try to lock it, you will receive the Status of 423 Locked.
Unlocking a Resource
Unlocking a resource is easy if you have a unique lock token. Simply send the UNLOCK command to the server and add a header that contains your lock token. If successful, the server will return the Status of 204 No Content. This means the command completed successfully but the server had no text to return except for the status. The following code uses a specific lock token to unlock a resource:
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder/my word doc.doc/"; request = new ActiveXObject("microsoft.xmlhttp"); request.open("UNLOCK", strURL, false); request.setRequestHeader("Lock-Token", "<opaquelocktoken:9641CB50-729A-4966-B904- 6F55773AA5B7:10582347518064984065>"); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Subscribing to a Resource
You can use WebDAV to subscribe to a resource. As a subscriber, you can receive notification about changes to a resource in one of two ways: you can have the server inform you when the resource changes, or you can poll the server for any changes to the resource. To create a subscription, use the SUBSCRIBE command and pass the URL you want to subscribe to, as in the following example. This example also sets the timeout for a subscription.
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder/my word doc.doc/"; request = new ActiveXObject("microsoft.xmlhttp"); request.open("SUBSCRIBE", strURL, false); request.setRequestHeader("Subscription-lifetime", 1000); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
If your subscription is successful, the server will return a Subscription-id, which you should keep because you'll need to pass it when you later poll the server or unsubscribe from the resource.
Polling the Server
To poll the server to see whether any of the resources you've subscribed to have changed, you need to use the POLL command. This command requires that you pass the Subscription-id that the SUBSCRIBE command gave you when you subscribed to the resource. The following code checks to see whether anything has changed on a resource. If no changes were made to the resource, the server will return a 204 status code.
<HTML> <BODY> <SCRIPT LANGUAGE=javascript> <!-- var strURL = "http://localhost/public/my new folder/my word doc.doc/"; request = new ActiveXObject("microsoft.xmlhttp"); request.open("POLL", strURL, false); request.setRequestHeader("Subscription-id", "SomeID"); request.send(); alert(request.status + " " + request.statustext); //--> </SCRIPT> </BODY> </HTML> |
Querying with WebDAV SEARCH
One of the neat things that you can do with WebDAV is perform SQL syntax queries against Exchange Server and have your results formatted as XML. Having your query returned to the client as XML is a very powerful capability, because it allows you to perform client-side formatting with XSL. Furthermore, since the XML is already on the client, you can resort the data very quickly. The following code is taken from the coursexml.asp file in the Training application. This code sends a SEARCH request to the Exchange server and receives the data from the server as XML.
request = new ActiveXObject("microsoft.xmlhttp"); //Example propfind //request.open("PROPFIND", URLSchedule, true); request.open("SEARCH", URLSchedule, true); //For propfind you can select a depth //request.setRequestHeader("depth", "1,noroot"); request.setRequestHeader("Content-type", "text/xml"); proplist = "<D:iscollection/><D:displayname/> <D:getlastmodified/><D:creationdate/> <C:instructoremail/><CAL:location/><O:Author/> <O:Manager/><O:Title/><H:subject/>"; //You can also do a propfind to find specific properties //body = "<?xml version='1.0'?>"; //body += "<D:propfind xmlns:D='DAV:' //xmlns:O='urn:schemas-microsoft-com:office:office' //xmlns:C='<%=strSchema%>' xmlns:CAL='urn:schemas:calendar:' //xmlns:H='urn:schemas:httpmail:'>"; body = "<searchrequest xmlns='DAV:'>"; body += "<sql>"; body += "SELECT \"<%=strSchema%>materialshttppath\" as materialshttppath," body += "\"<%=strSchema%>overallscore\" as overallscore,\"<%=strSchema%>rating\" as rating,"; body += "\"<%=strSchema%>materialsfilepath\" as materialsfilepath,\"<%=strSchema%>surveycount\" as surveycount,"; body += " \"<%=strSchema%>discussionurl\" as discussionurl,\"<%=strSchema%>prereqs\" as prereqs,"; body += "\"urn:schemas:httpmail:textdescription\" as description,\"<%=strSchema%>category\" as category,"; body += "\"urn:schemas:calendar:dtstart\" as starttime, \"urn:schemas:calendar:dtend\" as endtime,"; body += "\"DAV:iscollection\" as iscollection,\"DAV:href\" as href,"; body += "\"urn:schemas:httpmail:subject\" as subject,\"urn:schemas:calendar:location\" as location, \"<%=strSchema%>instructoremail\" as instructoremail FROM scope('shallow traversal of \"<%=strURLToSchedule%>\"') where \"DAV:ishidden\" = false AND \"DAV:isfolder\" = false"; //Add date restriction body += " AND \"urn:schemas:calendar:dtstart\" >&eq; CAST(\"<%=dISODateStart%>\" as 'dateTime')"; body += " AND \"urn:schemas:calendar:dtstart\" <&eq; CAST(\"<%=dISODateEnd%>\" as 'dateTime')"; body += "</sql>"; body += "</searchrequest>"; //For debugging //alert(body); //Propfind example //body += "<D:prop>"; //body += proplist; //body += "</D:prop>"; //body += "</D:propfind>"; request.onreadystatechange = dostatechange; msgdiv.innerHTML = "<font face='verdana' size='+1'>Loading…</font>"; request.send(body); |
The SELECT statement in this code uses column aliasing, which will make it easier to format the data using XSL. You'll see how to use XSL to format the XML data returned later in this section. The following code sample shows the raw XML data returned from this query, illustrating how custom and built-in schema can be queried and returned with XML:
<?xml version="1.0"?><a:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1- a29f-00aa00c14882/" xmlns:c="xml:" xmlns:d="urn:schemas-microsoft-com: office:office" xmlns:a="DAV:"><a:response><a:href>http://thomriznt5srv/ public/140/Training/Schedule/{8C35C44B-68EB-4651-AC3E-5C475923A7A1} .EML</a:href><a:propstat><a:status>HTTP/1.1 200 OK</a:status><a:prop> <materialshttppath>http://thomriznt5srv/public/140/Training/Course Materials/Leveraging XML in Exchange 2000/?Cmd=contents&View=Messages </materialshttppath><materialsfilepath>file://THOMRIZNT5SRV/Course Materials140/Leveraging XML in Exchange 2000</materialsfilepath> <discussionurl>http://thomriznt5srv/public/140/Training/Discussions/ Leveraging XML in Exchange 2000/?Cmd=contents&View=By Conversation Topic</discussionurl><prereqs>fejio</prereqs><description>fjeoj </description><category>dev</category><starttime b:dt="dateTime.tz"> 2000-03-08T21:00:00.000Z</starttime><endtime b:dt="dateTime.tz">2000-03- 08T23:00:00.000Z</endtime><iscollection b:dt="boolean">0</iscollection> <href>http://thomriznt5srv/public/140/Training/Schedule/{8C35C44B-68EB- 4651-AC3E-5C475923A7A1}.EML</href><subject>Leveraging XML in Exchange 2000</subject><location>43</location><instructoremail>thomriz@thomriznt 5dom.extest.microsoft.com</instructoremail></a:prop></a:propstat> <a:propstat><a:status>HTTP/1.1 404 Resource Not Found</a:status><a:prop> <overallscore/><rating/><surveycount/></a:prop></a:propstat></a:response> <a:response><a:href>http://thomriznt5srv/public/140/Training/Schedule/ {75BD5A83-09E7-47B7-A9F1-A75DD62F5BA7}.EML</a:href><a:propstat> <a:status>HTTP/1.1 200 OK</a:status><a:prop><prereqs>fjoi</prereqs> <description>fei</description><category>dev</category><starttime b:dt="dateTime.tz">2000-03-08T18:00:00.000Z</starttime><endtime b:dt="dateTime.tz">2000-03-08T19:00:00.000Z</endtime><iscollection b:dt="boolean">0</iscollection><href>http://thomriznt5srv/public/140/ Training/Schedule/{75BD5A83-09E7-47B7-A9F1-A75DD62F5BA7}.EML </href><subject>CDO and You</subject><location>43</location> <instructoremail>thomriz@thomriznt5dom.extest.microsoft.com</ instructoremail></a:prop></a:propstat><a:propstat><a:status>HTTP/ 1.1 404 Resource Not Found</a:status><a:prop><materialshttppath/> <overallscore/><rating/><materialsfilepath/><surveycount/><discussionurl/> </a:prop></a:propstat></a:response></a:multistatus> |
Persisted Search Folders
When using WebDAV, you can use the WebDAV search methods that we looked at earlier. Exchange 2000 provides the capability to create persisted search folders when using WebDAV. These search folders are like standard folders in that you can use a URL to access them and query them. Search folders can be created in any of your application hierarchies. You cannot, however, create search folders in the MAPI All Public Folders hierarchy. Also, you cannot create search folders using ADO.
Search folders allow you to offload to the server the task of finding new items that meet your SQL search criteria. For example, imagine you have an application that spans 10 folders under the root folder. In each folder, you need to find all the items with a specific property, such as items whose content classes are a certain type—say, urn:content-classes:mycc. Rather than querying Exchange every time you need to find items that meet this criterion, you could create a top-level search folder. This search folder would asynchronously add links to new items that meet the criterion (or criteria) you specified in the search folder. Your application could query the search folder rather than perform a deep traversal of all the application folders. Plus, search results are stored and dynamically updated by Exchange without requiring the clients to be connected or having to re-query the Exchange database. Search performance should be much greater with a search folder.
Creating a Search Folder
To create a search folder, all you need to do is issue an MKCOL command. The MKCOL command also specifies a DAV:searchrequest property that contains the SQL statement you want the search folder to perform. The following example shows how to create a search folder:
function SearchFolderCreate( folderURL, SQLQuery ) { var oXMLHTTP; oXMLHTTP = new ActiveXObject("Microsoft.XMLHTTP"); oXMLHTTP.Open("MKCOL", folderURL, false); strR = "<?xml version='1.0'?>"; strR += "<d:propertyupdate xmlns:d='DAV:'>"; strR += "<d:set><d:prop><d:searchrequest><d:sql>" + SQLQuery+ "</d:sql>"; strR += "</d:searchrequest></d:prop></d:set></d:propertyupdate>"; oXMLHTTP.SetRequestHeader("Content-type:", "text/xml"); oXMLHTTP.send(strR); if(! Req.Status == "207") { // Multi-Status response alert("An error has occurred!!"); } } |
If the command is successful, the server returns a 207 Multistatus response.
Search folders are just like regular folders in that they contain properties, but they are different in that they contain properties unique to them. Table 19-2 outlines the special properties for search folders.
Table 19-2. Properties unique to Search folders.
Property | Description |
---|---|
DAV:resourcetype | If the folder is a search folder, the value of this property will be <DAV:collection/><DAV:searchresults/>. |
DAV:searchrequest | This property contains the original SQL query for the persisted search. You cannot change this property. If you need to modify your search, you must delete your search folder and recreate it, or create a new search folder. |
DAV:searchtype | This property is set to dynamic by the Web Storage System. |
Searching a Persisted Search Folder
To query your Search folder, all you need to do is use either the WebDAV Search or PropFind methods that we looked at earlier, and then specify the search folder URL. Optionally, you can specify a Range header in your Search queries to return a certain number of rows. For example, you can specify that you want only the first 10 rows or the last 10 rows. The following examples show you how to use the Range header with XMLHTTP.
'Rows 10-20 and 40-50 Req.setRequestHeader "Range", "rows=10-20,40-50" 'Last 10 rows Req.setRequestHeader "Range", "rows=-10" 'From Row 10 to the end of the result set Req.setRequestHeader "Range", "rows=10-" 'Rows 1-10 and the last 10 rows Req.setRequestHeader "Range", "rows=1-10,-10" |
Using ADO to Retrieve XML Data from Exchange Server
There are two other ways you can retrieve XML data from Exchange Server. First, you can generate the XML data yourself by using ADO. For example, you could generate an XML document for your data and simply plug the values in this document for the properties from the ADO Fields collection. Not too pretty or easy a technique, although it is functional.
The second way you can retrieve XML data from Exchange Server is to leverage the XML persistence feature in ADO. ADO allows you to both load and save data in an XML format. The XML format must, however, adhere to the structure expected by ADO. To save data as XML, you just call the Save method of the Recordset object and pass in a location and adPersistXML (1).
The cool thing about using ADO with XML is that ADO can persist to the file system or directly to the ASP Response object. This means you can either save your Recordset to an XML file, or you can blast the data to the browser in ASP applications. You can also reload a Recordset from a correctly formatted XML document. We'll cover that feature later in the chapter when we talk about deploying the workflow portion of the Training application. The following XML document comes from the Training application data saved by ADO's XML features:
<xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882' xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882' xmlns:rs='urn:schemas-microsoft-com:rowset' xmlns:z='#RowsetSchema'> <s:Schema id='RowsetSchema'> <s:ElementType name='row' content='eltOnly' rs:updatable='true'> <s:AttributeType name='c0' rs:name='urn:schemas:mailheader:subject' rs:number='1' rs:nullable='true' rs:write='true'> <s:datatype dt:type='string' dt:maxLength='32768'/> </s:AttributeType> <s:AttributeType name='c1' rs:name='DAV:href' rs:number='2' rs:nullable='true'> <s:datatype dt:type='string' dt:maxLength='32768'/> </s:AttributeType> <s:AttributeType name='c2' rs:name='urn:schemas:calendar:dtstart' rs:number='3' rs:nullable='true' rs:write='true'> <s:datatype dt:type='dateTime' rs:dbtype='filetime' dt:maxLength='16' rs:precision='19' rs:fixedlength='true'/> </s:AttributeType> <s:AttributeType name='c3' rs:name='urn:schemas:calendar:dtend' rs:number='4' rs:nullable='true' rs:write='true'> <s:datatype dt:type='dateTime' rs:dbtype='filetime' dt:maxLength='16' rs:precision='19' rs:fixedlength='true'/> </s:AttributeType> <s:extends type='rs:rowbase'/> </s:ElementType> </s:Schema> <rs:data> <z:row c0='CDO and You' c1='file://./backofficestorage/ thomriznt5dom.extest.microsoft.com/Public Folders/140/Training/ Schedule/{75BD5A83-09E7-47B7-A9F1-A75DD62F5BA7}.EML' c2='2000-03-08T18:00:00' c3='2000-03-08T19:00:00'/> <z:row c0='Leveraging XML in Exchange 2000' c1= 'file://./backofficestorage/thomriznt5dom.extest.microsoft.com/ Public Folders/140/Training/Schedule/{8C35C44B-68EB-4651-AC3E- 5C475923A7A1}.EML' c2='2000-03-08T21:00:00' c3='2000-03-08T23:00:00'/> </rs:data> </xml> |
Using XSL to Format XML
Now that you've retrieved your XML data from Exchange Server, you're probably wondering how to display this data in your application. This is where XSL comes in. While XML provides a great way to describe data, it doesn't provide a way to display data. And while HTML provides a great way to display data, it doesn't provide a good method for describing data. XSL bridges the gap between XML and HTML so that you can support rich descriptions of data, while also supporting rich viewing of that data.
The following code, taken from coursexml.asp in the Training application, shows you how to use XSL to format the XML data returned from Exchange Server. Although I don't have room to cover everything XSL allows you to do, I will point out the major tasks you can perform with XSL. This code should help you get started:
<SCRIPT LANGUAGE="javascript"> var thexml; function Resort(){ var strProp = document.all.SortByProp.innerText; //Call sortfield sortfield(strProp); } </SCRIPT> <LABEL ID="SortByProp" style="display:none" onpropertychange="javascript:Resort()"></LABEL> <DIV id="datediv"></DIV> <BR> <div id=msgdiv> </div> <xml id=reportXSL> <xsl:template xmlns:xsl="uri:xsl" xmlns:d="DAV:" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:c="<%=strSchema%>" xmlns:h="urn:schemas:httpmail:" xmlns:cal="urn:schemas:calendar:" > <xsl:script> function getMyDate(objThis, szDateFormatString, szTimeFormatSTring) { var m_objDate = new Date(); var m_x=0; var gszDateString = ""; var szDate = objThis.text; var szSubStr = szDate.substring(5,7); if(szSubStr.charAt(0) == "0") { szSubStr = szSubStr.charAt(1); } m_objDate.setUTCFullYear(szDate.substring(0,4)); //Set Year m_objDate.setUTCMonth(Number(szSubStr)-1); //Set Month m_objDate.setUTCDate(szDate.substring(8,10)); //Set Date m_objDate.setUTCHours(szDate.substring(11,13)); //Set Hours m_objDate.setUTCMinutes(szDate.substring(14,16)); //Set Minutes m_objDate.setUTCSeconds(szDate.substring(17,19)); //Set Seconds var iNumHours = m_objDate.getHours(); var szFormattedTime = formatTime(m_objDate.getVarDate(), szTimeFormatSTring); var szFormattedDate = formatDate(m_objDate.getVarDate(), szDateFormatString); gszDateString = szFormattedDate.substring(0,szFormattedDate.length-1) + " " + szFormattedTime; return (gszDateString); } </xsl:script> <table id="XMLTable"> <TBODY> xsl:for-each select="d:multistatus/d:response" order-by="<%=Request.QueryString("SortBy")%>"> <xsl:if test="d:propstat/d:prop/href[.!='']"> <xsl:if test="d:propstat/d:prop/iscollection[.='0']"> <xsl:if test="d:propstat/d:prop/subject[.!='']"> <TR><TD> <LI> <A HREF='detaildrop.asp' title='Click to view more information about this course' onclick="vbscript:ExpandCollapse()"> <xsl:attribute name="ID"><xsl:value-of select="d:propstat/d:prop/href" /></xsl:attribute> <xsl:value-of select="d:propstat/d:prop/subject" /> </A> </LI> </TD></TR> <TABLE style="display: none"><xsl:attribute name= "ID">Details<xsl:value-of select="d:propstat/d:prop/href" /></xsl:attribute> <TR><TD> <B>Location:</B></TD><TD> <xsl:value-of select="d:propstat/d:prop/location" / > </TD></TR> <TR><TD><B>Instructor:</B></TD><TD> <!--turn into mailto--> <A><xsl:attribute name="href">mailto:<xsl:value-of select= "d:propstat/d:prop/instructoremail" /></xsl:attribute> <xsl:value-of select="d:propstat/d:prop/instructoremail" /> </A> </TD></TR> <TR><TD><B>Category:</B></TD><TD> <xsl:value-of select="d:propstat/d:prop/category" / > </TD></TR> <!--convert to the correct timezone --> <TR><TD><B>Start Time:</B></TD><TD> <xsl:for-each select="d:propstat/d:prop/starttime"> <xsl:eval>getMyDate(this,"MM-dd-yyyy", "h:mm tt")</xsl:eval></xsl:for-each> </TD></TR> <TR><TD><B>End Time:</B></TD><TD> <xsl:for-each select="d:propstat/d:prop/endtime"> <xsl:eval>getMyDate(this,"MM-dd-yyyy","h:mm tt")</xsl:eval></xsl:for-each> </TD></TR> <TR><TD><B>Pre-Requisites:</B></TD><TD> <xsl:value-of select="d:propstat/d:prop/prereqs" /> </TD></TR> <TR><TD><B>Description:</B></TD><TD> <xsl:value-of select="d:propstat/d:prop/description" /> </TD></TR> <xsl:if test="d:propstat/d:prop/materialsfilepath[.!='']"> <TR><TD><B>Course Materials:</B></TD> <TD> <A href=""><xsl:attribute name="onclick" >javascript:window.open('<xsl:value-of select= "d:propstat/d:prop/materialsfilepath" />'); window.event.returnValue=false;</xsl:attribute> File link to materials </A>   <A href=""><xsl:attribute name="onclick" >javascript:window.open('<xsl:value-of select= "d:propstat/d:prop/materialshttppath" />'); window.event.returnValue=false;</xsl:attribute> HTTP link to materials </A> </TD></TR> </xsl:if> <xsl:if test="d:propstat/d:prop/discussionurl[.!='']"> <TR><TD> <A style="color: olive" href="" title="Click here to view the discussion for this course."> <xsl:attribute name="onclick" >javascript:window.open('<xsl:value-of select= "d:propstat/d:prop/discussionurl" />'); window.event.returnValue=false;</xsl:attribute> View Discussion Group </A> </TD></TR> </xsl:if> <TR><TD> <xsl:choose> <xsl:when test="d:propstat/d:prop/starttime[. < '<%=TurnIntoIso(Date(),"end")%>']"> <!--Course has already taken place work --> <B>This course has already taken place.</B> </xsl:when> <xsl:otherwise> <A style="color: olive" href="" title="Click here to register for this course."><xsl:attribute name="onclick">javascript:window. open('register.asp?FullCourseURL=<xsl:value-of select="d:propstat/d:prop/href" />'); window.event.returnValue=false;</xsl:attribute> Register for this course </A> </xsl:otherwise> </xsl:choose> </TD></TR> <TR><TD><BR></BR></TD></TR> </TABLE> </xsl:if> </xsl:if> </xsl:if> </xsl:for-each> </TBODY> </table> </xsl:template> </xml> <script language="javascript"> var URLSchedule ="<%=strURLToSchedule%>"; function sortfield(sortby) { thenode = reportXSL.selectSingleNode("xsl:template/table/TBODY/xsl:for-each"); thenode.setAttribute("order-by", sortby); if (thexml.selectSingleNode("a:multistatus/a:response") == null) { msgdiv.innerHTML = "<B>No courses found.</B>"; } else{ msgdiv.innerHTML = thexml.transformNode(reportXSL.documentElement); } } |
You'll notice that the code contains an XSL template that combines XSL commands with HTML. The template also contains embedded JavaScript that allows the XSL to transform the XML data into correctly formatted HTML.
XSL Elements
Let's take a look at some of the most frequently used XSL elements.
XSL Value-of
Value-of will be one of the XSL elements you use the most. This element places the value of the node you specify as part of the element. Here is an example of this element, taken from the code in the previous section:
<xsl:value-of select="d:propstat/d:prop/category" /> |
XSL If
As you can guess by the name, the If element implements simple, -conditional logic. You can pass in the Language parameter for this element indicating the scripting language you want to use to evaluate script in your condition testing. The Test parameter is the actual condition you want to test for. The following code checks to see whether the subject of the item returned is not empty. You can test for multiple conditions by using the Choose, When, and Otherwise elements discussed next.
<xsl:if test="d:propstat/d:prop/subject[.!='']"> |
XSL Choose, When, and Otherwise
Use these three elements together when you require more complex conditional testing. You can use these three elements to implement an If…ElseIf…Else structure. The following code checks to see whether the start time of the course is less than the current time, which would mean the course has already taken place. If the start time is after the current time, a registration link is created for the course.
<xsl:choose> <xsl:when test="d:propstat/d:prop/starttime[. < '<%=TurnIntoIso(Date(),"end")%>']"> <!--Course has already taken place work --> <B>This course has already taken place.</B> </xsl:when> //Another xsl:when could go here <xsl:otherwise> <A style="color: olive" href="" title="Click here to register for this course."> <xsl:attribute name="onclick"> javascript:window.open('register.asp?FullCourseURL=<xsl:value-of select="d:propstat/d:prop/href" />'); window.event.returnValue=false;</xsl:attribute> Register for this course </A> </xsl:otherwise> </xsl:choose> |
XSL Attribute
Notice in the previous code example the use of the XSL Attribute element. This element allows you to put an attribute on an HTML element inside your XSL template. You should do this if, as part of your HTML element, you want to evaluate another XSL element as the HTML element's value. In the previous example, the onclick attribute is added to the A element in the HTML. The href to the item is added as the value for the onclick attribute by using the Value-of XSL element.
XSL For-Each
The XSL For-Each element is similar to Visual Basic's For Each … Next loop. The For-Each element allows you to apply a template to an element. The best example of how you can use For-Each can be found in the first For-Each element that appears in the previous section's code. This For-Each element uses a Select clause and pattern matching to select only the response nodes in the XML. Furthermore, this example uses the Order-by criteria to support sorting the data by a specific node in the XML. The code example from the previous section follows:
<xsl:for-each select="d:multistatus/d:response" order-by="<%=Request.QueryString("SortBy")%>"> |
XSL Script and XSL Eval
You'll probably want to use the XSL Script and Eval elements together in your template. The Script element allows you specify a global script that the rest of your XSL template can call. You can pass the Script element a Language parameter that specifies the scripting language, such as JavaScript, for your script code.
The Eval element evaluates a script expression and generates a text string. You'll usually need to call a script you defined by using the Script element in your Eval element and have that script return a text value. You can however place in-line script in the Eval element as well. The following code gets the date of the course and correctly formats it using the Script and Eval elements:
<xsl:script> function getMyDate(objThis, szDateFormatString, szTimeFormatSTring) { var m_objDate = new Date(); var m_x=0; var gszDateString = ""; var szDate = objThis.text; var szSubStr = szDate.substring(5,7); if(szSubStr.charAt(0) == "0") { szSubStr = szSubStr.charAt(1); } m_objDate.setUTCFullYear(szDate.substring(0,4)); //Set Year m_objDate.setUTCMonth(Number(szSubStr)-1); //Set Month m_objDate.setUTCDate(szDate.substring(8,10)); //Set Date m_objDate.setUTCHours(szDate.substring(11,13)); //Set Hours m_objDate.setUTCMinutes(szDate.substring(14,16)); //Set Minutes m_objDate.setUTCSeconds(szDate.substring(17,19)); //Set Seconds var iNumHours = m_objDate.getHours(); var szFormattedTime = formatTime(m_objDate.getVarDate(), szTimeFormatSTring); var szFormattedDate = formatDate(m_objDate.getVarDate(), szDateFormatString); gszDateString = szFormattedDate.substring(0,szFormattedDate.length- 1) + " " + szFormattedTime; return (gszDateString); } </xsl:script> . . . <!--convert to the correct time zone --> <TR><TD><B>Start Time:</B></TD><TD> <xsl:for-each select="d:propstat/d:prop/starttime"> <xsl:eval>getMyDate(this,"MM-dd-yyyy","h:mm tt") </xsl:eval></xsl:for-each> </TD></TR> <TR><TD><B>End Time:</B></TD><TD> <xsl:for-each select="d:propstat/d:prop/endtime"> <xsl:eval>getMyDate(this,"MM-dd-yyyy","h:mm tt") </xsl:eval></xsl:for-each> |
While I've barely begun to scratch the surface of XSL, this overview should help you get started in transforming your XML using XSL. The best resource I have found on XSL is the MSDN library at http://msdn.microsoft.com/. Check out the Web Workshop section in the Platform SDK. Not only does it include lots of documentation on XML, it includes a wealth of information on XSL.