Interacting with the Unified Messaging Web Service

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

Topic Last Modified: 2008-05-20

By David Sterling

As you may know by now, Microsoft Exchange Server 2007 provides a new Web services API that can be used to access Exchange resources such as mailboxes and, starting with Exchange 2007 Service Pack 1 (SP1), public folders. You might also be aware that, instead of a single unified Web service, Exchange 2007 actually includes two Web services: Exchange Web Services and the Unified Messaging (UM) Web service. Unified Messaging Web Service? Yes, it is there in all its glory, hidden in the jungle of the Client Access server. However, finding it requires you to put on your explorer hat, pull out your machete, and fearlessly plunge into the wilderness.

Talking to the UM Web Service

Given that UM is a Web service, you should be able to add a Web reference in Microsoft Visual Studio, or run a proxy generator tool such as wsdl.exe or svcutil.exe. Unfortunately, however, automatic WSDL documentation has been turned off for the UM Web service, and no explicit WSDL or schema files exist that you can reference for the UM Web service.

So what are you to do? Let’s think about this for a moment. Web services communicate over a protocol such as HTTP and carry a SOAP/XML payload. What happens when you add a Web reference in Visual Studio or use a tool such as wsdl.exe or svcuils.exe to generate proxy classes for a Web service? Well, the tool examines the XML structure that is defined in the WSDL document and creates methods for each operation that are object representations of a request, and converts those object instances into XML to send to the Web service. When the Web service responds, those client-side classes convert the response XML into object instances that represent the data that is contained in the XML response. The important thing to note is that, although you as a proxy class consumer are blissfully unaware of it, an underlying XML dance is occurring. In fact, the Web service itself is completely unaware that you are using an autogenerated proxy class. All it sees is raw XML.

A WSDL file that defines the contract to use to communicate with the Exchange UM Web service does not exist. The UM Web service does have a public contract, however, although not in a machine-readable format. The 11 UM Web methods are defined on MSDN, including what the incoming and outgoing XML should look like. For more information about these Web methods, see Unified Messaging Web Service Reference. Although you cannot generate a proxy class from this documentation, you can put on your cape and become a Super Proxy Generator yourself.

Given that you do have access to the structure of the requests that the UM Web service expects, you can write your own set of proxy classes that simplify interaction with the UM Web service. The purpose of this article is not only to give you a usable set of proxy classes for UM Web service interaction, but also to give you a better understanding of what occurs behind the scenes when applications talk to any Web service.

Building the UM Proxy Classes

In this section I will describe how to build a UM proxy class.

Making a Raw SOAP Call

The Microsoft .NET Framework offers several classes that you can use to make HTTP POST requests to the UM Web service. To assist you, you will create a class called HttpHelper that will contain all the HTTP request and response processing, and handle the SOAP-related items, including the envelope, header, body, and SOAP faults.

Listing 1   Static constructor

/// <summary>
/// Handles SOAP requests to a given server.
/// </summary>
internal static class HttpHelper
{
    private static XmlNamespaceManager namespaceManager;

    /// <summary>
    /// Static constructor
    /// </summary>
    static HttpHelper()
    {
        // Create our static XmlNamespaceManager, which will be used to perform 
        // XPath queries against
        // responses.
   
        namespaceManager = new XmlNamespaceManager(new NameTable());
        namespaceManager.AddNamespace(
             "soap", 
             "https://schemas.xmlsoap.org/soap/envelope/");
        namespaceManager.AddNamespace(
             "m", 
             "https://schemas.microsoft.com/exchange/services/2006/messages");
    }
// More...

In Listing 1, you define the HttpHelper class and its static constructor. The static constructor is of interest because that is where you create your XmlNamespaceManager static instance. Because you will be dealing with raw XML, you will use XPath queries to find the relevant response elements. You can use the XmlNamespaceManager to perform those queries without worrying about the namespace prefix that the Web service used when it generated the response. For example, you call the AddNamespace method to associate the SOAP prefix with the namespace https://schemas.xmlsoap.org/soap/envelope. However, a Web service could return a response that uses the “s” prefix instead (or “fdsafdsa” for that matter). When you use the XmlNamespaceManager, namespaces are matched based on the fully expanded XML namespace, while you have the convenience of referring to them by the prefix that you provide to the namespace manager.

Listing 2 shows the MakeRawSoapRequest static method. This is where the meat of this class will reside.

Listing 2   MakeRawSoapRequest method

  /// <summary>
  /// Makes a raw SOAP request.
  /// </summary>
  /// <param name="url">The URL of the server to talk to.</param>
  /// <param name="credentials">The credentials to use for the call. This value is null if 
  /// you want to use default credentials.</param>
  /// <param name="requestString">The contents of the SOAP body (minus the 
  /// body element).</param>
  /// <param name="headers">Adds SOAP headers.</param>
  /// <returns>The XML DOM wrapper around the response.</returns>
  /// 
  public static XmlDocument MakeRawSoapRequest(
                                           string url,
                                           NetworkCredential credentials,
                                           string requestString,
                                           params string[] headers)
  {
   // Create the actual request. 
   HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
   request.Method = "POST";
   request.ContentType = "text/xml;utf-8";

   // If credentials were passed in, use them. Otherwise, make the request as 
   // the account that is running the current thread.
   if (credentials == null)
   {
     request.UseDefaultCredentials = true;
    }
    else
    {
      request.Credentials = credentials;
    }

    // Include all the SOAP elements and put the actual request in the body.
    StringBuilder builder = new StringBuilder();
    builder.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
    builder.AppendLine(
     "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
    builder.AppendLine(
           "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"");
    builder.AppendLine(
           "xmlns:soap=\"https://schemas.xmlsoap.org/soap/envelope/\"");
    builder.AppendLine(
      "xmlns=\"https://schemas.microsoft.com/exchange/services/2006/messages\"");
    builder.AppendLine(
      "xmlns:t=\"https://schemas.microsoft.com/exchange/services/2006/types\">");

    // Add the SOAP headers if they exist (no SOAP headers exist for UM requests).
    if ((headers != null) && (headers.Length > 0))
    {
       builder.AppendLine("<soap:Header>");
       foreach (string header in headers)
       {
         builder.Append(header);
       }
       builder.AppendLine("</soap:Header>");
     }

     builder.AppendLine("<soap:Body>");

     // Add the request that was passed.
     builder.Append(requestString);

     // Close out the remaining tags.
     builder.AppendLine("</soap:Body>");
     builder.AppendLine("</soap:Envelope>");

     // Grab the request as a byte array. We must write it to the request 
     // stream.
     byte[] requestBytes = Encoding.UTF8.GetBytes(builder.ToString());
     request.ContentLength = requestBytes.Length;

     // Write our request bytes to the Web request.
     //
     using (Stream requestStream = request.GetRequestStream())
     {
       requestStream.Write(requestBytes, 0, requestBytes.Length);
       requestStream.Flush();
       requestStream.Close();
     }

     HttpWebResponse response;
     try
     {
       // Try to get the response. If a failure occurred, this will throw a 
       // WebException.
       response = request.GetResponse() as HttpWebResponse;
     }
     catch (WebException webException)
     {
       // We issued an HttpWebRequest, so the response will be an 
       // HttpWebResponse.
       HttpWebResponse httpResponse = webException.Response as HttpWebResponse;

       // Grab the response stream.
       using (Stream responseStream = httpResponse.GetResponseStream())
       {
         using (StreamReader reader = new StreamReader(responseStream))
         {
           // Grab the body of the response as a string for parsing.
           string failureReason = reader.ReadToEnd();

           // If it failed with a SOAP fault, parse the response body into a 
           // usable exception.
           ThrowIfSoapFault(failureReason);

           // If it was not a SOAP fault, rethrow the original exception.
           throw;
         }
       }
     }

     // Read the response stream. This was a good response.
     string responseString;
     using (Stream responseStream = response.GetResponseStream())
     {
       using (StreamReader reader = new StreamReader(responseStream))
       {
         responseString = reader.ReadToEnd();
       }
     }

     // Now we have our response. Load it into the XML DOM and return it as an 
     // XmlDocument.
     return ExtractDocumentFromResponse(responseString);
   }
//... More...

The first part of Listing 2 is self explanatory. You use a StringBuilder object to build up the XML text that you want to send and then make the request by using the HttpWebRequest class. What is interesting is how you deal with the response. After a Web request is made (by writing to and flushing the request stream), you retrieve the Web response by calling GetResponse on your HttpWebRequest instance. If the response comes back with an HTTP status code of 200 (OK), the call to GetResponse will be successful. However, if the HTTP response that is returned by the Web server indicates an error status code (such as 500), the call to GetResponse will throw a WebException.

In the case that the response indicates an HTTP status code of 200 (OK), you cast the response to an HttpWebResponse instance. You then process the response like you did the request, although in reverse order (retrieve the response stream, read out the bytes, and convert them to text). In contrast, when GetResponse throws a WebException, you are not given the HttpWebResponse, at least not directly.

Listing 3 shows the try/catch block from Listing 2 again.

Listing 3   Try/Catch block when retrieving the response

     try
     {
       // Try to get the response. If there was a failure, this will throw a 
       // WebException.
       response = request.GetResponse() as HttpWebResponse;
     }
     catch (WebException webException)
     {
       // We issued an HttpWebRequest, so the response will be an 
       // HttpWebResponse.
       HttpWebResponse httpResponse = webException.Response as HttpWebResponse;

       // Grab the response stream.
       using (Stream responseStream = httpResponse.GetResponseStream())
       {
         using (StreamReader reader = new StreamReader(responseStream))
         {
           // Grab the body of the response as a string for parsing.
           string failureReason = reader.ReadToEnd();

           // If it failed with a SOAP fault, parse the response body into a 
           // usable exception.
           ThrowIfSoapFault(failureReason);

           // If it was not a SOAP fault, rethrow the original exception.
           throw;
         }
       }
     }

Within the catch block in Listing 3, notice that you do indeed have access to the Web response that is returned by the server. The response is exposed via the Response property on the WebException. However, to retrieve anything interesting from the response, you have to cast it to an instance of HttpWebResponse. After you have the HttpWebResponse instance that contains the error response message, you can read the contents from the response stream and convert the contents to text, just as you did for a successful response. After you retrieve the response text, you then examine it to see what type of failure it represents.

Most Web services return failure information in SOAP faults, which are SOAP messages that contain a Fault element within the SOAP body. The details about the failure are contained within the various child elements of the Fault element. SOAP faults can also contain application-specific failure information in the optional Details element. However, because the Details element is open-ended, you do not typically parse the contents of the Details element for fun. Therefore, you add a method called ThrowIfSoapFault that examines the contents of the error response. If the response is a failure, the method throws a more usable exception that contains both the reason for the failure (obtained from the Faultstring element) and the UM application–specific ExceptionType element that is contained within the SOAP fault details.

Listing 4 shows the ThrowIfSoapFault method.

Listing 4   ThrowIfSoapFault method

  /// <summary>
  /// Throws a UMFaultException if the response contains a SOAP fault.
  /// </summary>
  /// <param name="responseFailure">The response body to parse.</param>
  /// 
  private static void ThrowIfSoapFault(string responseFailure)
  {
    XmlDocument doc = ExtractDocumentFromResponse(responseFailure);
    if (doc == null)
    {
      // The response did not include an XML 
      // document. Return so we can rethrow the original exception.
      return;
    }

    // UM SOAP faults have two elements of interest. The Faultstring element contains a 
    // textual representation of what happened. The ExceptionType element contains 
    // the class name of the server-side exception that was thrown.
    XmlNodeList faultStringNodes = doc.SelectNodes(
              "//faultstring[1]", namespaceManager);
    XmlNodeList exceptionTypeNodes = doc.SelectNodes(
              "//m:ExceptionType[1]", namespaceManager);
    if ((faultStringNodes.Count == 0) || (exceptionTypeNodes.Count == 0))
    {
      // The response did not contain the FaultString or ExceptionType elements.
      return;
    }

    // Throw a more useful exception that includes the information that is of interest.
    throw new UmFaultException(
          exceptionTypeNodes[0].InnerText, 
          faultStringNodes[0].InnerText);
  }
//... More...

In Listing 4, you call the ExtractDocumentFromResponse method, which uses regular expressions to look for a SOAP envelope within the response string, and populates an XmlDocument instance with the envelope contents. Listing 5 shows the ExtractDocumentFromResponse method.

Listing 5   ExtractDocumentFromResponse method

  /// <summary>
  /// Extract the XML document from the Web response.
  /// </summary>
  /// <param name="response">The Web response to extract the XML document from.</param>
  /// <returns>XmlDocument</returns>
  /// 
  internal static XmlDocument ExtractDocumentFromResponse(string response)
  {
    // The namespace prefix might not be s: or soap:, so match any non-white-space 
    // content before Envelope.
    Match match = Regex.Match(response, @"<\S*:Envelope");
    if (match.Success)
    {
       XmlDocument doc = new XmlDocument();
       doc.LoadXml(response.Substring(match.Index));
       return doc;
    }
    return null;
  }

Listing 6 shows the UmFaultException class that is thrown if a SOAP fault is encountered.

Listing 6   UmFaultException class

/// <summary>
/// The exception that is thrown when a SOAP fault is encountered in a call to UM Web methods.
/// </summary>
public class UmFaultException : Exception
{
  private string exceptionType;
  #region exception types

  /// <summary>
  /// This list is provided for your convenience and may not be conclusive. 
  /// </summary>
  public const string UserNotUmEnabledException = "UserNotUmEnabledException";
  public const string InvalidCallIdException = "InvalidCallIdException";
  public const string InvalidObjectIdException = "InvalidObjectIdException";
  public const string UMServerNotFoundDialPlanException = 
                                         "UMServerNotFoundDialPlanException";
  public const string IPGatewayNotFoundException = "IPGatewayNotFoundException";
  #endregion

  /// <summary>
  /// Constructor
  /// </summary>
  /// <param name="exceptionType">ExceptionType is parsed from the Details 
  /// element.</param>
  /// <param name="reason">The Faultstring reason from the SOAP fault.</param>
  /// 
  public UmFaultException(string exceptionType, string reason)
      : base(reason)
  {
    this.exceptionType = exceptionType;
  }

  /// <summary>
  /// Accessor for the ExceptionType string. Note that the faultstring is 
  /// accessing via the Message property on an exception instance.
  /// </summary>
  public string ExceptionType
  {
    get
    {
      return this.exceptionType;
    }
  }
}

Notice that Listing 6 includes several constant strings for the ExceptionType values. These enable you to do comparisons such as the following:

  // Code removed for brevity...
  catch (UmFaultException umFaultException)
  {
    if (umFaultException.ExceptionType == UmFaultException.InvalidCallIdException)
    { 
      // Do something...
    }
  }

UnifiedMessagingBinding Class

Now that you have a way to make raw SOAP calls, you could just run with it; however, doing this would require building up the UM request strings each time that you want to call UM. And of course, you must pull the data out of the resulting XmlDocument instances that MakeRawSoapRequest gives you. We can do better than that.

When you add a Web reference to a Web service in Visual Studio or run the wsdl.exe tool against a WSDL file, a binding class is generated that exposes all the available Web methods as instance methods on the binding class. Let’s follow this approach and create the UnifiedMessagingBinding class. Listing 7 starts you out in this direction.

Listing 7   The UnifiedMessagingBinding class

/// <summary>
/// Serves as the binding class that is used to talk to the UM Web service.
/// </summary>
public class UnifiedMessagingBinding
{
  #region members
  private string url;
  private NetworkCredential credentials;
  private bool useDefaultCredentials;
  internal static XmlNamespaceManager namespaceManager;
  #endregion

  #region request strings
  private static readonly string urlFormat =
        "https://{0}/unifiedmessaging/service.asmx";
  private static readonly string messagesNamespace = 
    "xmlns=\"https://schemas.microsoft.com/exchange/services/2006/messages\">";
  private static readonly string IsUmEnabledRequest = 
        "<IsUMEnabled " + messagesNamespace + "</IsUMEnabled>";
  private static readonly string DisconnectRequest =
        "<Disconnect " + messagesNamespace + 
                "<CallId>{0}</CallId></Disconnect>";
  private static readonly string GetCallInfoRequest =
        "<GetCallInfo " + messagesNamespace + 
                "<CallId>{0}</CallId></GetCallInfo>";
  private static readonly string GetUMPropertiesRequest =
        "<GetUMProperties " + messagesNamespace + "</GetUMProperties>";
  private static readonly string PlayOnPhoneRequest =
        "<PlayOnPhone " + messagesNamespace + 
        "  <entryId>{0}</entryId>" +
        "  <DialString>{1}</DialString>" +
        "</PlayOnPhone>";
  private static readonly string PlayOnPhoneGreetingRequest =
        "<PlayOnPhoneGreeting " + messagesNamespace + 
        "  <GreetingType>{0}</GreetingType>" + 
        "  <DialString>{1}</DialString>" + 
        "</PlayOnPhoneGreeting>";
  private static readonly string ResetPinRequest =
        "<ResetPIN " + messagesNamespace + "</ResetPIN>";
  private static readonly string SetMissedCallNotificationEnabledRequest =
        "<SetMissedCallNotificationEnabled " + messagesNamespace + 
        "  <status>{0}</status>" + 
        "</SetMissedCallNotificationEnabled>";
  private static readonly string SetOofStatusRequest =
        "<SetOofStatus " + messagesNamespace + 
        "  <status>{0}</status>" +
        "</SetOofStatus>";
  private static readonly string SetPlayOnPhoneDialStringRequest =
        "<SetPlayOnPhoneDialString " + messagesNamespace + 
        "  <dialString>{0}</dialString>" + 
        "</SetPlayOnPhoneDialString>";
  private static readonly string SetTelephoneAccessFolderEmailRequest =
        "<SetTelephoneAccessFolderEmail " + messagesNamespace + 
        "  <base64FolderID>{0}</base64FolderID>" +
        "</SetTelephoneAccessFolderEmail>";
  #endregion request strings
//... More...

UM is a Web service that is located somewhere. It doesn’t matter how beautiful your requests look if you send them to the wrong place. In Exchange 2007, the UM Web service methods and the other Exchange Web Services methods are not located at the same URL. Instead, the URL for accessing the UM Web service methods looks like this:

https://yourClientAccessServer/unifiedmessaging/service.asmx

YourClientAccessServer is the host name of the computer that is running Exchange 2007 that has the Client Access server role installed. This is the same server that hosts Exchange Web Services and Outlook Web Access.

Therefore, UnifiedMessagingBinding holds a private format string that is used to generate the correct URL for your host:

  private static readonly string urlFormat =
                "https://{0}/unifiedmessaging/service.asmx";

After this, Listing 7 shows us that UnifiedMessagingBinding contains many private read-only strings, each of which contains the basic structure for one of the 11 UM Web service method requests. Note that all the requests, responses, and supporting types are defined within the messages namespace, which is one of the namespaces that Exchange Web Services uses. Do not, however, expect to find any of the UM schema types within the messages.xsd file.

Listing 8 shows a set of constructors for the UnifiedMessagingBinding class.

Listing 8   UnifiedMessagingBinding constructor

//... Code removed for brevity...
/// <summary>
/// Static constructor
/// </summary>
/// 
static UnifiedMessagingBinding()
{
  // If you have a valid certification on your Exchange server, you can 
  // remove the following line. It is included here for the testing of self-signed 
  // certificates.
  System.Net.ServicePointManager.ServerCertificateValidationCallback =
        delegate(Object obj, X509Certificate certificate, X509Chain chain, 
                 SslPolicyErrors errors)
  {
    return true;
  };

  // Create a managed namespace, which will be used for XPath queries.
  namespaceManager = new XmlNamespaceManager(new NameTable());
  namespaceManager.AddNamespace("m", 
         "https://schemas.microsoft.com/exchange/services/2006/messages");
}

/// <summary>
/// Constructor 
/// </summary>
/// <param name="hostname">Host name (not the full URL).</param>
/// <param name="credentials">The credentials to use when making the UM calls.</param>
/// 
public UnifiedMessagingBinding(string hostname, NetworkCredential credentials)
{
  this.url = String.Format(urlFormat, hostname);
  this.credentials = credentials;
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="hostname">Hostname (not the full URL).</param>
/// 
public UnifiedMessagingBinding(string hostname)
{
  this.url = String.Format(urlFormat, hostname);
  this.useDefaultCredentials = true;
}

Listing 8 shows the single static constructor and the two instance constructor overloads for the UnifiedMessagingBinding class. The static constructor adds a delegate to handle server certificate errors. When Exchange 2007 is installed, the Client Access servers are configured to use self-signed certificates. We do not recommend that you trust servers that have self-signed certificates. Therefore, when it makes an HTTPS request, the .NET Framework will throw an exception when it encounters self-signed server certificates, unless you intercept the condition first by registering a handler for the ServicePointManager.ServerCertificateValidationCallback delegate. In Listing 8, you blindly accept any certificate that we are given, which is fine for initial testing, but not a good idea for deployed applications. After it sets up the server certificate callback, the static constructor configures another XmlNamespaceManager that will be used when parsing response documents.

After the static constructor, Listing 8 offers two instance constructors. The first constructor takes a NetworkCredential instance that indicates the user name, password, and domain that should be used when making the request. The second constructor does not take a NetworkCredential instance, but instead relies on the default credentials for the account that the current thread is running under. Both instance constructors also take the host name of the Client Access server, which is then used to generate the URL for the UM Web service.

Before getting into the actual UM Web service methods, the UnifiedMessagingBinding class provides a helper method called MakeRequest that further simplifies the building of the request and the parsing of the response. MakeRequest is shown in Listing 9.

Listing 9   MakeRequest method

/// <summary>
/// Makes a request and returns the inner response element.
/// </summary>
/// <param name="request">The request to send out.</param>
/// <param name="responseElementName">The local name of the response element.</param>
/// <param name="headers"> SOAP headers (optional). None for UM.</param>
/// <returns>Response node.</returns>
/// 
private XmlNode MakeRequest(
                    string request, 
                    string responseElementName, 
                    params string[] headers)
{
  XmlDocument doc = HttpHelper.MakeRawSoapRequest(
                                    this.url,
                                    this.useDefaultCredentials ? 
                                          null : 
                                          this.credentials,
                                    request,
                                    headers);

  if (String.IsNullOrEmpty(responseElementName))
  {
    // The caller is not interested in the response.
    return null;
   }
 
  // UM has an interesting nesting of elements such that the local name 
  // occurs twice for all responses.
  XmlNodeList nodes = doc.SelectNodes(
                   String.Format(
                       "//m:{0}/m:{0}[1]", 
                       responseElementName), 
                   namespaceManager);
  return nodes[0];
}

As shown in Listing 9, MakeRequest calls HttpHelper.MakeRawSoapRequest to make the actual Web request. However, notice that it takes into account the credentials that should be used based on the UnifiedMessagingBinding constructor that was called. After it receives the response document, MakeRequest performs additional processing that will simplify your life a bit.

All UM Web service method responses contain a pair of nested elements that have the same name and namespace. For example, the IsUmEnabled Web service method returns a response in the following format:

<?xml version="1.0" encoding="utf-8" ?>
  <soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" 
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <soap:Body>
      <IsUMEnabledResponse 
            xmlns="https://schemas.microsoft.com/exchange/services/2006/messages"> 
        <IsUMEnabledResponse>true</IsUMEnabledResponse> 
    </IsUMEnabledResponse>
  </soap:Body>
</soap:Envelope>

We are really interested in the inner IsUMEnabledResponse element, which contains the Boolean value that indicates whether UM is enabled on the caller’s mailbox. Given this, you can use the following XPath query to return the inner IsUMEnabledResponse element:

//m:IsUMEnabledResponse/m:IsUMEnabledResponse

In fact, because all the UM Web service methods have responses that follow this pattern, you can abstract this by using a format string for our XPath query:

XmlNodeList nodes = doc.SelectNodes(
          String.Format(
              "//m:{0}/m:{0}[1]", 
          responseElementName), 
          namespaceManager);

Given this response structure, MakeRequest takes the responseElementName as one of its arguments, which is then used to generate the XPath query for extracting the inner element out of the response. Of course, several methods do not have to extract information from the response. Therefore, if responseElementName is null, the method bails out before the response is processed.

Assuming, however, that the responseElementName was indeed passed in, the XPath query is run against the response and MakeRequest returns the first matching node. Note that adding the [1] expression to the end of the XPath query makes it slightly more efficient, as the XPath evaluator will stop processing the document after the first match is found. If [1] is omitted, the evaluator will continue to process the rest of the document looking for matches.

UM Web Methods

Now that the infrastructure has been established, adding the UM Web service methods is easy, as shown in Listing 10. Except for the inline comments, no additional explanation will be given for these methods.

Listing 10   The UM Web methods

...Previous code removed for brevity...
/// <summary>
/// IsUmEnabled Web service method. Indicates whether UM is enabled on the calling 
/// account's mailbox.
/// </summary>
/// <returns>True if UM is enabled.</returns>
/// 
public bool IsUmEnabled()
{
  XmlNode response = MakeRequest(IsUmEnabledRequest, "IsUMEnabledResponse");
  return bool.Parse(response.InnerText);
}

/// <summary>
/// Disconnect Web service method. Disconnects a currently running call.
/// </summary>
/// <param name="callId">The Id of the call to disconnect.</param>
/// 
public void Disconnect(string callId)
{
  MakeRequest(DisconnectRequest, null /*not interested in response*/);
}

/// <summary>
/// GetCallInfo Web service method  Returns information about the specified call.
/// </summary>
/// <param name="callId">The Id of the call to retrieve information for.</param>
/// <returns>GetCallInfo response</returns>
/// 
public GetCallInfoResponse GetCallInfo(string callId)
{
  XmlNode response = MakeRequest(
          String.Format(GetCallInfoRequest, callId),
          "GetCallInfoResponse");
  return GetCallInfoResponse.Parse(response);
}

/// <summary>
/// Returns UM properties for the caller's mailbox.
/// </summary>
/// <returns>UM properties</returns>
/// 
public GetUMPropertiesResponse GetUMProperties()
{
  XmlNode response = MakeRequest(
               GetUMPropertiesRequest,
               "GetUMPropertiesResponse");
  return GetUMPropertiesResponse.Parse(response);
}

/// <summary>
/// Plays the specified message on the telephone indicated by the dial string.
/// </summary>
/// <param name="entryId">PR_ENTRYID of the message to play on the telephone.</param>
/// <param name="dialString">The dial string to use when calling the telephone.</param>
/// <returns>The CallId of ensuing telephone call.</returns>
/// 
public string PlayOnPhone(string entryId, string dialString)
{
  XmlNode responseNode = MakeRequest(
           String.Format(PlayOnPhoneRequest, entryId, dialString),
           "PlayOnPhoneResponse");
  return responseNode.InnerText;
}

/// <summary>
/// Plays either the regular or OOF greeting on the telephone indicated by the dial string.
/// </summary>
/// <param name="greetingType">GreetingType (regular or OOF)</param>
/// <param name="dialString">The dial string to use when calling the telephone.</param>
/// <returns>The CallId of ensuing telephone call.</returns>
/// 
public string PlayOnPhoneGreeting(GreetingType greetingType, string dialString)
{
  XmlNode responseNode = MakeRequest(
     String.Format(PlayOnPhoneGreetingRequest, greetingType.ToString(), dialString),
     "PlayOnPhoneGreetingResponse");
  return responseNode.InnerText;
}

/// <summary>
/// Resets the UM PIN for the mailbox of the current caller.
/// </summary>
public void ResetPin()
{
  MakeRequest(ResetPinRequest, null /* ignore the response */);
}

/// <summary>
/// Sets the enabled status of the missed call notification for the current
/// caller's mailbox.
/// </summary>
/// <param name="enabled">Either true or false</param>
/// 
public void SetMissedCallNotificationEnabled(bool enabled)
{
  MakeRequest(
       String.Format(SetMissedCallNotificationEnabledRequest, 
           enabled.ToString()),
       null /* ignore the response */);
}

/// <summary>
/// Sets the OOF status for the mailbox in question.
/// </summary>
/// <param name="enabled">Either true or false</param>
/// 
public void SetOofStatus(bool enabled)
{
  MakeRequest(
      String.Format(SetOofStatusRequest, enabled.ToString()),
      null /* ignore the response */);
}

/// <summary>
/// Sets the play on telephone dial string.
/// </summary>
/// <param name="dialString">Dial string</param>
/// 
public void SetPlayOnPhoneDialString(string dialString)
{
  MakeRequest(
         String.Format(SetPlayOnPhoneDialStringRequest, dialString),
          null /* ignore the response */);
}

/// <summary>
/// Sets the folder ID that is used to retrieve e-mail messages for playback.
/// </summary>
/// <param name="base64FolderId">base64 PR_ENTRYID for folder</param>
/// 
public void SetTelephoneAccessFolderEmail(string base64FolderId)
{
  MakeRequest(
       String.Format(SetTelephoneAccessFolderEmailRequest, base64FolderId),
       null /* ignore the response */);
}

In GetCallInfo and GetUMProperties, the parsing of the response has been delegated off to factory methods on the GetCallInfoResponse (Listing 11) and GetUmPropertiesResponse (Listing 12) classes respectively.

Listing 11   GetCallInfoResponse class

/// <summary>
/// Response information for GetCallInfo.
/// </summary>
public class GetCallInfoResponse
{
  private CallState callState;
  private EventCause eventCause;

  /// <summary>
  /// Factory method for creating GetCallInfoResponse from response XML.
  /// </summary>
  /// <param name="responseNode">Response xml node</param>
  /// <returns>GetCallInfoResponse</returns>
  /// 
  public static GetCallInfoResponse Parse(XmlNode responseNode)
  {
    XmlNode callStateNode = responseNode.ChildNodes[0];
    XmlNode eventCauseNode = responseNode.ChildNodes[1];
    CallState callState = (CallState)Enum.Parse(
                  typeof(CallState),
                  callStateNode.InnerText);
    EventCause eventCause = (EventCause)Enum.Parse(
                  typeof(EventCause), 
                  eventCauseNode.InnerText);
    return new GetCallInfoResponse(callState, eventCause);
  }

  /// <summary>
  /// Private constructor. Calling code should use factory method instead.
  /// </summary>
  /// <param name="callState">CallState</param>
  /// <param name="eventCause">EventCause</param>
  /// 
  private GetCallInfoResponse(CallState callState, EventCause eventCause)
  {
    this.callState = callState;
    this.eventCause = eventCause;
  }

  /// <summary>
  /// Accessor for CallState
  /// </summary>
  public CallState CallState
  {
    get { return this.callState; }
  }

  /// <summary>
  /// Accessor for EventCause
  /// </summary>
  public EventCause EventCause
  {
    get { return this.eventCause; }
  }
}

Listing 12   GetUMPropertiesResponse class

/// <summary>
/// Response information for GetUMProperties
/// </summary>
public class GetUMPropertiesResponse
{
  private bool oofStatus;
  private bool missedCallNotificationEnabled;
  private string playOnPhoneDialString;
  private string[] telephoneAccessNumbers;
  private string telephoneAccessFolderEmail;

  /// <summary>
  /// Factory method
  /// </summary>
  /// <param name="responseNode">the response node to parse.</param>
  /// <returns>GetUMPropertiesResponse</returns>
  /// 
  public static GetUMPropertiesResponse Parse(XmlNode responseNode)
  {
    GetUMPropertiesResponse result = new GetUMPropertiesResponse();
    result.oofStatus = bool.Parse(responseNode.ChildNodes[0].InnerText);
    result.missedCallNotificationEnabled = 
              bool.Parse(responseNode.ChildNodes[1].InnerText);
    result.playOnPhoneDialString = responseNode.ChildNodes[2].InnerText;
    XmlNode telephoneAccessNumbersNode = responseNode.ChildNodes[3];
    if (telephoneAccessNumbersNode.HasChildNodes)
    {
      result.telephoneAccessNumbers = new 
            string[telephoneAccessNumbersNode.ChildNodes.Count];
      for (int index = 0; index<telephoneAccessNumbersNode.ChildNodes.Count;
           index++)
      {
        XmlNode childNode = telephoneAccessNumbersNode.ChildNodes[index];
        result.telephoneAccessNumbers[index] = childNode.InnerText;
      }
    }
    result.telephoneAccessFolderEmail = responseNode.ChildNodes[4].InnerText;
    return result;
  }

  /// <summary>
  /// Accessor for OofStatus
  /// </summary>
  public bool OofStatus
  {
    get { return this.oofStatus; }
  }

  /// <summary>
  /// Accessor for MissedCallNotificationEnabled
  /// </summary>
  public bool MissedCallNotificationEnabled
  {
    get { return this.missedCallNotificationEnabled; }
  }

  /// <summary>
  /// Accessor for PlayOnPhoneDialString
  /// </summary>
  public string PlayOnPhoneDialString
  {
    get { return this.playOnPhoneDialString; }
  }

  /// <summary>
  /// Accessor for TelephoneAccessNumbers. CAN BE NULL.
  /// </summary>
  public string[] TelephoneAccessNumbers
  {
    get { return this.telephoneAccessNumbers; }
  }

  /// <summary>
  /// Accessor for TelephoneAccessFolderEmail
  /// </summary>
  public string TelephoneAccessFolderEmail
  {
    get { return this.telephoneAccessFolderEmail; }
  }
}

Using the UM Proxy Classes

Using the UM proxy classes is straightforward, as shown in Listing 13. Just create an instance of the UnifiedMessagingBinding class and then call the methods of interest. If a failure occurs, a UmFaultException will be thrown.

Listing 13   Using the UnifiedMessagingBinding class

try
{
  UnifiedMessagingBinding binding = new UnifiedMessagingBinding(
                   "yourCASServer",
                   new NetworkCredential("username", "password", "domain"));
  bool isEnabled = binding.IsUmEnabled();
  Console.WriteLine("IsUmEnabled? " + isEnabled.ToString());
}
catch (UmFaultException umFaultException)
{
   Console.WriteLine("Fault encountered. Message: '{0}', ExceptionType: {1}",
            umFaultException.Message,
            umFaultException.ExceptionType);
}

Exchange Web Services, Meet Unified Messaging

Although the UM proxy classes are interesting in themselves, they become more interesting when you combine them with Exchange Web Services. For example, what good is a PlayOnPhone Web service method unless you have an entry Id for a message that you want to play on the telephone? While the integration possibilities are numerous, I offer here a single example.

Suppose that you want to find all the unread messages within the last day from your manager and then play each message on the telephone. First, use the Exchange Web Services method FindItem with a restriction to return the appropriate messages. The request in Listing 14 will give you the messages that you are interested in.

Listing 14   Finding the messages of interest

<FindItem xmlns="https://schemas.microsoft.com/exchange/services/2006/messages"
          xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types" 
          Traversal="Shallow">
  <ItemShape>
    <t:BaseShape>IdOnly</t:BaseShape>
    <t:AdditionalProperties>
      <t:ExtendedFieldURI PropertyTag="0x0FFF" PropertyType="Binary"/>
    </t:AdditionalProperties>
  </ItemShape>
  <Restriction>
    <t:And>
      <t:IsEqualTo>
        <t:FieldURI FieldURI="message:IsRead"/>
        <t:FieldURIOrConstant>
          <t:Constant Value="false"/>
        </t:FieldURIOrConstant>
      </t:IsEqualTo>
      <t:IsGreaterThan>
        <t:FieldURI FieldURI="item:DateTimeReceived"/>
        <t:FieldURIOrConstant>
          <t:Constant Value="12/14/2007"/>
        </t:FieldURIOrConstant>
      </t:IsGreaterThan>
      <t:Contains ContainmentMode="Substring" ContainmentComparison="Loose">
        <t:FieldURI FieldURI="message:Sender"/>
        <t:Constant Value="Mr. BossMan"/>
      </t:Contains>
    </t:And>
  </Restriction>
  <ParentFolderIds>
    <t:DistinguishedFolderId Id="inbox"/>
  </ParentFolderIds>
</FindItem>

Of interest in Listing 14 is the fact that you request the PR_ENTRYID for the messages via the extended property path in the AdditionalProperties element of the ItemShape. Why is this important? Because the UM Web service deals with native MAPI PR_ENTRYIDs instead of Exchange Web Services ItemIds.

Listng 15 shows the response.

Listing 15   FindItemResponse

<m:FindItemResponse 
           xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types" 
           xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages">
  <m:ResponseMessages>
    <m:FindItemResponseMessage ResponseClass="Success">
      <m:ResponseCode>NoError</m:ResponseCode>
      <m:RootFolder TotalItemsInView="2" IncludesLastItemInRange="true">
        <t:Items>
          <t:Message>
            <t:ItemId Id="AAAeAGRhdnN..." ChangeKey="CQAAABYAAADc.../>
            <t:ExtendedProperty>
              <t:ExtendedFieldURI PropertyTag="0xfff" PropertyType="Binary"/>
              <t:Value>AAAAAIUn...</t:Value>
            </t:ExtendedProperty>
          </t:Message>
          <t:Message>
            <t:ItemId Id="AAAeAGRhdnN0..." ChangeKey="CQAAABYAAA..."/>
            <t:ExtendedProperty>
              <t:ExtendedFieldURI PropertyTag="0xfff" PropertyType="Binary"/>
              <t:Value>AAAAAIUnJ7skJ...</t:Value>
            </t:ExtendedProperty>
          </t:Message>
        </t:Items>
      </m:RootFolder>
    </m:FindItemResponseMessage>
  </m:ResponseMessages>
</m:FindItemResponse>

Listing 15 shows that there are two messages in my Inbox from my manager that have not been read. I can therefore extract the contents of the Value element for each ExtendedProperty and use that to pass off to PlayOnPhone. You can do this all by using the proxy classes, assuming that you have already generated a set of proxy classes for Exchange Web Services.

Listing 16 provides the GetMessageIdsToPlay method, which issues the request from Listing 14 and extracts the PR_ENTRYIDs from the response. Note that GetMessageIdsToPlay calls BuildRestriction, which builds the restriction to use for the FindItem call. The restriction that is returned by the BuildRestriction method is important because without it, all the messages in your Inbox will be played on your telephone.

Listing 16   GetMessageIdsToPlay

/// <summary>
/// Use Exchange Web Services to get the messages of interest.
/// </summary>
/// <returns>Array of messages to retrieve.</returns>
/// 
private static string[] GetMessageIdsToPlay()
{
  ExchangeServiceBinding ewsBinding = new ExchangeServiceBinding();
  ewsBinding.Url = String.Format("https://{0}/ews/Exchange.asmx", 
         "yourCASServer");
  ewsBinding.UseDefaultCredentials = true;

  FindItemType findRequest = new FindItemType();
  // We are only interested in the PR_ENTRYID, so request a BaseShape of IdOnly
  // and add PR_ENTRYID as an additional property.
  ItemResponseShapeType responseShape = new ItemResponseShapeType();
  responseShape.BaseShape = DefaultShapeNamesType.IdOnly;
  PathToExtendedFieldType entryIdPath = new PathToExtendedFieldType();
  entryIdPath.PropertyTag = "0x0FFF";
  entryIdPath.PropertyType = MapiPropertyTypeType.Binary;
  responseShape.AdditionalProperties = new BasePathToElementType[] { entryIdPath };
  findRequest.ItemShape = responseShape;

  // We are looking in the Inbox for these messages.
  DistinguishedFolderIdType inbox = new DistinguishedFolderIdType();
  inbox.Id = DistinguishedFolderIdNameType.inbox;
  findRequest.ParentFolderIds = new BaseFolderIdType[] { inbox };
  findRequest.Restriction = BuildRestriction();
  findRequest.Traversal = ItemQueryTraversalType.Shallow;

  FindItemResponseType response = ewsBinding.FindItem(findRequest);
  FindItemResponseMessageType responseMessage = 
      response.ResponseMessages.Items[0] as 
          FindItemResponseMessageType;

  if (responseMessage.ResponseCode != ResponseCodeType.NoError)
  {
    throw new Exception("FindItem failed with response code: " + 
           responseMessage.ResponseCode.ToString());
  }

  // For each ItemType within the RootFolder, extract the first ExtendedProperty.
  ItemType[] items = (responseMessage.RootFolder.Item as 
       ArrayOfRealItemsType).Items;
  string[] responseIds = new string[items.Length];
  for (int index = 0; index < items.Length; index++)
  {
    ItemType item = items[index];
    responseIds[index] = (string)item.ExtendedProperty[0].Item;
  }
  return responseIds;
}


/// <summary>
/// Build our restriction (filter).
/// </summary>
/// <returns>Restriction</returns>
/// 
private static RestrictionType BuildRestriction()
{
  // <t:And>
  AndType and = new AndType();

  /*<t:IsEqualTo>
      <t:FieldURI FieldURI="message:IsRead"/>
      <t:FieldURIOrConstant>
        <t:Constant Value="false"/>
      </t:FieldURIOrConstant>
    </t:IsEqualTo>*/

  IsEqualToType isEqualTo = new IsEqualToType();
  PathToUnindexedFieldType isReadPath = new PathToUnindexedFieldType();
  isReadPath.FieldURI = UnindexedFieldURIType.messageIsRead;
  isEqualTo.Item = isReadPath;
  FieldURIOrConstantType isReadComparison = new FieldURIOrConstantType();
  ConstantValueType isReadConstant = new ConstantValueType();
  isReadConstant.Value = "false";
  isReadComparison.Item = isReadConstant;
  isEqualTo.FieldURIOrConstant = isReadComparison;

  /*<t:IsGreaterThan>
      <t:FieldURI FieldURI="item:DateTimeReceived"/>
      <t:FieldURIOrConstant>
        <t:Constant Value="10/10/2007"/>
      </t:FieldURIOrConstant>
    </t:IsGreaterThan>*/

  IsGreaterThanType isGreaterThan = new IsGreaterThanType();
  PathToUnindexedFieldType dateTimeReceivedPath = 
         new PathToUnindexedFieldType();
  dateTimeReceivedPath.FieldURI = UnindexedFieldURIType.itemDateTimeReceived;
  isGreaterThan.Item = dateTimeReceivedPath;
  FieldURIOrConstantType dateTimeReceivedComparison = new
           FieldURIOrConstantType();
  ConstantValueType dateTimeReceivedConstant = new ConstantValueType();
  dateTimeReceivedConstant.Value = DateTime.Now.AddDays(-7d).ToString();
  dateTimeReceivedComparison.Item = dateTimeReceivedConstant;
  isGreaterThan.FieldURIOrConstant = dateTimeReceivedComparison;

  /*<t:Contains ContainmentMode="Substring" ContainmentComparison="Loose">
      <t:FieldURI FieldURI="message:Sender"/>
      <t:Constant Value="Mr. BossMan"/>
    </t:Contains> */
  ContainsExpressionType contains = new ContainsExpressionType();
  contains.ContainmentComparison = ContainmentComparisonType.Loose;
  contains.ContainmentComparisonSpecified = true;
  contains.ContainmentMode = ContainmentModeType.Substring;
  contains.ContainmentModeSpecified = true;
  PathToUnindexedFieldType senderPath = new PathToUnindexedFieldType();
  senderPath.FieldURI = UnindexedFieldURIType.messageSender;

  contains.Item = senderPath;
  contains.Constant = new ConstantValueType();
  contains.Constant.Value = "Mr. BossMan";

  // Combine these into a single And expression.
  //
           isEqualTo,
           isGreaterThan,
           contains};

  RestrictionType result = new RestrictionType();
  result.Item = and;
  return result;
 }

After you have the PR_ENTRYIDs that you want to play, pass those off in turn to PlayOnPhone. This is shown in Listing 17.

Listing 17   PlayMessagesOnPhone

/// <summary>
/// Play the specified messages on the telephone.
/// </summary>
/// <param name="entryIds">Array of base64-encoded entry Ids.</param>
/// <param name="phoneNumber">Telephone number to dial.</param>
/// 
private static void PlayMessagesOnPhone(string[] entryIds, string phoneNumber)
{
  // Create our binding.
  UnifiedMessagingBinding umBinding = new 
          UnifiedMessagingBinding("yourCASServer");

  foreach (string entryId in entryIds)
  {
    umBinding.PlayOnPhone(entryId, phoneNumber);
  }
}

And finally, put these two calls together as follows:

static void Main(string[] args)
{
  string[] messageIds = GetMessageIdsToPlay();
  // call 9-1-555-555-1212
  //
  PlayMessagesOnPhone(messageIds, "915555551212");
}

Concluding Thoughts

You can expand upon the ideas in this example to create very useful tools. For example, by using Exchange Web Service Push Notifications, you can register for New Mail events on your Inbox. Whenever a new message arrives, Exchange Web Services will alert your application. Your application can then retrieve the new item and determine whether it meets a certain set of criteria (from your manager, marked as important, and so on). If it meets the criteria, you can have Unified Messaging call your mobile phone. This can be very useful if you have to step out of the office for a while but want to be able to respond to important e-mail messages quickly. For the downloadable version of the sample code, go to the Exchange Download Center.