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=\"http://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",
"http://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="http://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="http://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.