Testing sample which uses HttpWebRequest to do a POST to EWS
There are sometimes issues encountered which are difficult to resolve. There are many things which can cause issues which would call an EWS call to fail. Having well written with extensive error trapping and extensive logging is one approach. I wrote the sample below so that it could be used from any type .NET application and only uses no .NET code. So, there is no Exchange Managed API involved here. So, if your trying to reproduce an issue, get logs or are just looking for ways to improve your code then please look over this sample and use it as desired.
// This example shows how to do an EWS call using HttpWebRequest from .NET.
// The EWS Managed API is not used here - nor is any other API. Yes, it's all .NET code.
// It can be used as example or built into an application for testing.
// It should be fairly easy to adapt this sample to a Winform, web page or service.
// You will need to review and modify the TODO sections as needed.
// It handles the SSL redirect and also will log to the application event log.
// If an issue with the SSL certificate is detected then it will be reported with details on the certificates.
// This code was written and tested with .NET 4.5 and Visual Studio 2013. However, it should work with earlier versions of .NET and Visual Studio.
// If a redirect is encountered then then code will issue another POST using the redirect URL. This will result in an an additional logging event.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
namespace TestEws
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private static string _sCertificateErrors = string.Empty;
private void button1_Click(object sender, EventArgs e)
{
bool bRet = false;
bool bLogAuthorizationHeader = true; // TODO: Set to false to not log the Authorization header.
bool bProxyToFiddler = false; // TODO: Set to true to proxy through a locally installed Fiddler.
string sRequestBody = string.Empty;
string sRequest = string.Empty;
string sResponse = string.Empty;
string sError = string.Empty;
string sStatusInfo = string.Empty;
string sRedirectUrl = string.Empty;
_sCertificateErrors = string.Empty;
string sUrl = "https://outlook.office365.com/EWS/Exchange.asmx"; // TODO: Point this to the Exchange Web Service
string sUser = "someone@contoso.com"; // TODO: Set to the UPN of the account to authentcate with. It usually matches the smtp address.
string sPassword = "abc123"; // TODO: Set to the password
string sMailboxToGetOofFor = "someone@contoso.com"; // TODO: Set to the mailbox being accessed.
sRequestBody = GetOffRequestString(sMailboxToGetOofFor);
NetworkCredential oNetworkCredential = null;
oNetworkCredential = new NetworkCredential(sUser, sPassword);
// Normally SSL is used with EWS, so handle the SSL callback.
ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
bRet = DoSoapRequest(
sUrl,
oNetworkCredential,
bLogAuthorizationHeader,
bProxyToFiddler,
sRequestBody,
ref sRequest,
ref sResponse,
ref sError,
ref sStatusInfo,
ref sRedirectUrl
);
LogResults(sUrl, sUser, sMailboxToGetOofFor, sStatusInfo, sError, sRequest, sResponse, sRedirectUrl, bRet);
// Redirect was found.
if (sRedirectUrl != string.Empty)
{
bRet = false;
// Do another post - but this time use the redirect URL.
bRet = DoSoapRequest(
sRedirectUrl,
oNetworkCredential,
bLogAuthorizationHeader,
bProxyToFiddler,
sRequestBody,
ref sRequest,
ref sResponse,
ref sError,
ref sStatusInfo,
ref sRedirectUrl
);
LogResults(sUrl, sUser, sMailboxToGetOofFor, sStatusInfo, sError, sRequest, sResponse, sRedirectUrl, bRet);
}
}
// LogResults
// Log what happened when the EWS POST was done.
private void LogResults(string sUrl,
string sUser,
string sMailboxToGetOofFor,
string sStatusInfo,
string sError,
string sRequest,
string sResponse,
string sRedirectUrl,
bool bEwsCallSucceeded)
{
// Uncomment the block below to display results to screen in a winform
//if (bRet == true)
// MessageBox.Show(sResponse, "Call completed");
//else
// MessageBox.Show(sError, "Error");
StringBuilder oSB = new StringBuilder();
if (sRedirectUrl != string.Empty)
oSB.AppendFormat("Note: A redirect to '{0}' was found. Another POST will be done after this one.\r\n", sUrl);
oSB.AppendFormat("EWS POST to URL: {0}\r\n", sUrl);
oSB.AppendFormat("Authenticating User: {0}\r\n", sUser);
oSB.AppendFormat("Mailbox: {0}\r\n", sMailboxToGetOofFor);
oSB.AppendLine("\r\n----\r\n\r\n");
if (sStatusInfo != string.Empty)
{
oSB.AppendFormat("Status Info: \r\n{0}\r\n", sStatusInfo);
oSB.AppendLine("\r\n----\r\n\r\n");
}
if (sError != string.Empty)
{
oSB.AppendFormat("Error: {0}\r\n", sError);
oSB.AppendLine("\r\n----\r\n\r\n");
}
if (sRequest != string.Empty)
oSB.AppendFormat("Request: \r\n\r\n{0}\r\n\r\n", sRequest);
if (sResponse != string.Empty)
oSB.AppendFormat("Response: \r\n\r\n{0}\r\n\r\n", sResponse);
oSB.AppendFormat("Certificates: \r\n\r\n{0}\r\n\r\n", _sCertificateErrors);
string sLogThis = oSB.ToString();
//// Display results in a basic MessageBox.
//MessageBox.Show(sLogThis, "Call completed");
// Display results in a dialog window.
// To use: Create a winform, add a multi-line scrolling text control,
// anchor it in all four directions and set it to Public.
ShowResults oForm = new ShowResults();
oForm.textBox1.Text = sLogThis;
oForm.ShowDialog();
// Log results to the Windows Application event log.
System.Diagnostics.EventLog appLog = new System.Diagnostics.EventLog();
appLog.Source = "Application";
if (bEwsCallSucceeded == true)
appLog.WriteEntry(sLogThis, EventLogEntryType.Information, 1);
else
appLog.WriteEntry(sLogThis, EventLogEntryType.Error, 2);
}
// DoSoapRequest
// sEwsUrl – this is the url to the exchange.asmx
// oCredentials - initialized credentials object.
// bLogAuthorizationHeader = Log the authorization header used in the POST. Some may not want this logged for secruity reasons.
// This will cause the call to be proxied.
// EWSRequestBody - The body to use in the POST
// EWSRequestString – EWS request info.
// EWSResponseString – The response info.
// sStatusInfo - String of the status of the POST.
public bool DoSoapRequest(string sEwsUrl,
ICredentials oCredentials,
bool bLogAuthorizationHeader,
bool bProxyToFiddler,
string EWSRequestBody,
ref string EWSRequestString,
ref string EWSResponseString,
ref string EWSErrors,
ref string sStatusInfo,
ref string sRedirectUrl)
{
// Clear ref parameters
EWSRequestString = string.Empty;
EWSResponseString = string.Empty;
EWSErrors = string.Empty;
sStatusInfo = string.Empty;
sRedirectUrl = string.Empty;
string sRequestHeaders = string.Empty;
string sResponeHeaders = string.Empty;
string sResponseFound = string.Empty;
string sErrorFound = string.Empty;
string EWSStatusString = string.Empty;
string sResponseStatusCodeNumber = string.Empty;
string sResponseStatusCode = string.Empty;
int iResponseStatusCodeNumber = 0;
string sResponseStatusDescription = string.Empty;
// string sError = string.Empty;
bool bSuccess = true;
HttpWebRequest oHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create(sEwsUrl);
oHttpWebRequest.Method = "POST";
oHttpWebRequest.ContentType = "text/xml;utf-8";
oHttpWebRequest.Headers.Add("Pragma", "no-cache");
oHttpWebRequest.Headers.Add("return-client-request-id", "true"); // Tell exchange to return client-request-id so the client side logging & server logging can be matched.
oHttpWebRequest.Headers.Add("UserAgent", "MyTestApp");
//oHttpWebRequest.Headers.Add(“X-AnchorMailbox”, sMailbox); //Set to the mailbox being accessed if EWS Impersonation is used.
oHttpWebRequest.Credentials = oCredentials;
if (bProxyToFiddler == true)
ProxyToLocalFiddler(ref oHttpWebRequest);
byte[] requestBytes = System.Text.Encoding.UTF8.GetBytes(EWSRequestBody);
oHttpWebRequest.ContentLength = requestBytes.Length;
try
{
using (Stream requestStream = oHttpWebRequest.GetRequestStream()) // Get a connection to the server.
{
requestStream.Write(requestBytes, 0, requestBytes.Length);
requestStream.Flush();
requestStream.Close();
}
}
catch (Exception ex)
{
sErrorFound = "Error trying to access the EWS web service: " + ex.ToString();
if (bProxyToFiddler == true)
sErrorFound += "\r\n\r\n----\r\n\r\n\r\n **** Note that the code is trying to proxy.**** ";
EWSErrors = sErrorFound;
bSuccess = false;
return false;
}
// get request headers for logging.
string sHeaderValue = string.Empty;
string sReqHeader = string.Empty;
if (oHttpWebRequest.Headers != null)
{
StringBuilder oSB_RequestHeaders = new StringBuilder();
foreach (string key in oHttpWebRequest.Headers.AllKeys)
{
if (oHttpWebRequest.Headers[key] != null)
sHeaderValue = oHttpWebRequest.Headers[key];
else
sHeaderValue = "";
sReqHeader = string.Format("{0}: {1}", key, sHeaderValue);
if (bLogAuthorizationHeader != false || key != "Authorization")
oSB_RequestHeaders.AppendLine(sReqHeader);
}
sRequestHeaders = oSB_RequestHeaders.ToString();
}
HttpWebResponse oHttpWebResponse = null;
try
{
oHttpWebResponse = oHttpWebRequest.GetResponse() as HttpWebResponse; // Do EWS call
}
catch (WebException webException)
{
string sBodyOfStreamWithError = string.Empty;
HttpWebResponse httpResponse = webException.Response as HttpWebResponse;
using (Stream responseStream = httpResponse.GetResponseStream())
{
StreamReader reader = new StreamReader(responseStream);
sBodyOfStreamWithError = reader.ReadToEnd();
//MessageBox.Show(sError, "WebException");
bSuccess = false;
}
string sErrorHeaders = string.Empty;
string sHeader = string.Empty;
string sValue = string.Empty;
if (webException.Response.Headers != null)
{
StringBuilder oSB_ResponseHeaders = new StringBuilder();
foreach (string key in webException.Response.Headers.AllKeys)
{
if (webException.Response.Headers[key] != null)
sValue = webException.Response.Headers[key];
else
sValue = "";
sHeader = string.Format("{0}: {1}", key, sValue);
oSB_ResponseHeaders.AppendLine(sHeader);
}
sResponeHeaders = oSB_ResponseHeaders.ToString();
}
sErrorFound = "WebExceptionError:\r" + webException.ToString() + "\r\r";
if (sErrorHeaders != string.Empty || sBodyOfStreamWithError != string.Empty)
sErrorFound += "----\r\r";
if (sErrorHeaders != string.Empty)
sErrorFound += "Exception Response Headers:\r" + sErrorHeaders + "\r\r";
if (sBodyOfStreamWithError != string.Empty)
sErrorFound += "Body of exception stream:\r" + sBodyOfStreamWithError + "\r\r";
if (webException.Status != null)
{
sResponseStatusCode = webException.Status.ToString();
iResponseStatusCodeNumber = (int)webException.Status;
sResponseStatusDescription = "";
}
}
catch (System.Net.Sockets.SocketException ex)
{
sErrorFound = ex.Message.ToString();
bSuccess = false;
}
catch (Exception ex)
{
sErrorFound = ex.Message.ToString();
bSuccess = false;
}
// Check for some sort of a redirect.
if (oHttpWebResponse != null)
{
if ((oHttpWebResponse.StatusCode == HttpStatusCode.Found) ||
(oHttpWebResponse.StatusCode == HttpStatusCode.Redirect) ||
(oHttpWebResponse.StatusCode == HttpStatusCode.Moved) ||
(oHttpWebResponse.StatusCode == HttpStatusCode.MovedPermanently))
{
sRedirectUrl = oHttpWebResponse.Headers["Location"];
}
}
// Read Response Stream
if (bSuccess == true)
{
string sResponseFinal = string.Empty;
try
{
using (Stream responseStream = oHttpWebResponse.GetResponseStream())
{
StreamReader reader = new StreamReader(responseStream, Encoding.ASCII);
sResponseFinal = reader.ReadToEnd();
}
}
catch (Exception ex)
{
sErrorFound = "Error reading stream of response: " + ex.Message.ToString();
bSuccess = false;
}
sResponseFound = sResponseFinal;
string sHeader = string.Empty;
try
{
// Get Response headers:
if (oHttpWebResponse != null)
{
if (oHttpWebResponse.Headers != null)
{
StringBuilder oSB_ResponseHeaders = new StringBuilder();
string sResponseHeader = string.Empty;
foreach (string key in oHttpWebResponse.Headers.AllKeys)
{
if (oHttpWebResponse.Headers[key] != null)
sResponseHeader = oHttpWebResponse.Headers[key];
else
sResponseHeader = "";
sHeader = string.Format("{0}: {1}", key, sResponseHeader);
oSB_ResponseHeaders.AppendLine(sHeader);
}
sResponeHeaders = oSB_ResponseHeaders.ToString();
}
}
}
catch (Exception ex)
{
sErrorFound = "Error reading headers: " + ex.Message.ToString();
bSuccess = false;
}
}
try
{
if (oHttpWebResponse != null)
{
sResponseStatusCode = oHttpWebResponse.StatusCode.ToString();
iResponseStatusCodeNumber = (int)oHttpWebResponse.StatusCode;
sResponseStatusDescription = oHttpWebResponse.StatusDescription;
}
}
catch (Exception ex)
{
sErrorFound = "Error reading response status: " + ex.Message.ToString();
bSuccess = false;
}
EWSErrors = sErrorFound;
EWSRequestString =sRequestHeaders + "\r\n" + EWSRequestBody;
EWSResponseString = sResponeHeaders + "\r\n" + sResponseFound;
StringBuilder oStatusInfo = new StringBuilder();
oStatusInfo.AppendFormat("ResponseStatusCode: {0}\r\n", sResponseStatusCode);
oStatusInfo.AppendFormat("ResponseCodeNumber: {0}\r\n", iResponseStatusCodeNumber);
oStatusInfo.AppendFormat("ResponseStatusDescription: {0}\r\n", sResponseStatusDescription);
sStatusInfo = oStatusInfo.ToString();
return bSuccess;
}
private string GetOffRequestString(string sMailbox)
{
StringBuilder oSB = new StringBuilder();
oSB.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
oSB.AppendLine("<soap:Envelope xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\" ");
oSB.AppendLine("xmlns:m=\"https://schemas.microsoft.com/exchange/services/2006/messages\" ");
oSB.AppendLine("xmlns:t=\"https://schemas.microsoft.com/exchange/services/2006/types\" ");
oSB.AppendLine("xmlns:soap=\"https://schemas.xmlsoap.org/soap/envelope/\">");
oSB.AppendLine("<soap:Header>");
oSB.AppendLine(" <t:RequestServerVersion Version=\"Exchange2010_SP1\"/>");
oSB.AppendLine("</soap:Header> ");
oSB.AppendLine("<soap:Body>");
oSB.AppendLine(" <m:GetUserOofSettingsRequest>");
oSB.AppendLine(" <t:Mailbox>");
oSB.AppendFormat(" <t:Address>{0}</t:Address>\r\n", sMailbox);
oSB.AppendLine(" </t:Mailbox>");
oSB.AppendLine(" </m:GetUserOofSettingsRequest> ");
oSB.AppendLine(" </soap:Body> ");
oSB.AppendLine("</soap:Envelope>");
return oSB.ToString();
}
private void ProxyToLocalFiddler(ref HttpWebRequest oHttpWebRequest)
{
string ProxyServerName = "127.0.0.1"; // Local address
int ProxyServerPort = 8888; // Default port used by Fiddler
WebProxy oWebProxy = null;
oWebProxy = new WebProxy(ProxyServerName, ProxyServerPort);
oWebProxy.BypassProxyOnLocal = false; // see: https://msdn.microsoft.com/en-us/library/system.net.webproxy.bypassproxyonlocal(v=vs.110).aspx
oWebProxy.UseDefaultCredentials = true;
//// TODO: To set credentials to the proxy you can use one of the lines below:
//oWebProxy.Credentials = new NetworkCredential(ProxyServerUser, ProxyServerPassword);
//oWebProxy.Credentials = new NetworkCredential(ProxyServerUser, ProxyServerPassword, ProxyServerDomain);
oHttpWebRequest.Proxy = oWebProxy;
}
private static void LogCertificateInfo(ref string sInfo)
{
_sCertificateErrors += sInfo + "\r\n";
}
private static bool CertificateValidationCallBack(
object sender,
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
string sLine = string.Empty;
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
sLine = "Valid certificate found.\r\n";
LogCertificateInfo(ref sLine);
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
if (chain != null && chain.ChainStatus != null)
{
foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus)
{
if ((certificate.Subject == certificate.Issuer) &&
(status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot))
{
// Self-signed certificates with an untrusted root are valid.
sLine = "A self-signed certificates with an untrusted root was found. Handling as valid.";
LogCertificateInfo(ref sLine);
DumpCertificationDetail(certificate, chain);
continue;
}
else
{
if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError)
{
// If there are any other errors in the certificate chain, the certificate is invalid,
// so the method returns false.
sLine = "SSL certificate validation failed." + status.StatusInformation + "\r\n";
LogCertificateInfo(ref sLine);
sLine = "System.Net.Security.SslPolicyErrors: " + System.Enum.GetName(typeof(System.Net.Security.SslPolicyErrors), sslPolicyErrors) + "\r\n";
LogCertificateInfo(ref sLine);
DumpCertificationDetail(certificate, chain);
return false;
}
}
}
}
// When processing reaches this line, the only errors in the certificate chain are
// untrusted root errors for self-signed certificates. These certificates are valid
// for default Exchange server installations, so return true.
return true;
}
else
{
// In all other cases, return false.
sLine = "Unknown issue with certificates.\r\n";
LogCertificateInfo(ref sLine);
DumpCertificationDetail(certificate, chain);
return false;
}
}
public static void DumpCertificationDetail(
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain)
{
string sLines = string.Empty;
StringBuilder certificateDetail = new StringBuilder();
certificateDetail.AppendLine("SSL Certificate detail:");
certificateDetail.AppendLine(". . .");
certificateDetail.AppendLine("X509Certificate");
certificateDetail.AppendLine(". . .");
certificateDetail.AppendLine(certificate.ToString(true));
certificateDetail.AppendLine();
certificateDetail.AppendLine(". . .");
certificateDetail.AppendLine("X509Chain");
certificateDetail.AppendLine("ChainContext: " + chain.ChainContext.ToString());
//builder.AppendLine("ChainPolicy: " + chain.ChainPolicy.);
certificateDetail.AppendLine("ChainStatus: ");
foreach (X509ChainStatus status in chain.ChainStatus)
{
certificateDetail.AppendLine("\tChainStatus.Status:" + status.Status.ToString());
certificateDetail.AppendLine("\tChainStatus.StatusInformation:" + status.StatusInformation);
}
certificateDetail.AppendLine(". . .");
foreach (X509ChainElement element in chain.ChainElements)
{
certificateDetail.AppendLine(". . .");
certificateDetail.AppendLine("X509ChainElement");
certificateDetail.AppendLine("ChainElementStatus:");
foreach (X509ChainStatus status in element.ChainElementStatus)
{
certificateDetail.AppendLine("\tChainElementStatus.Status:" + status.Status.ToString());
certificateDetail.AppendLine("\tChainElementStatus.StatusInformation:" + status.StatusInformation);
}
certificateDetail.AppendLine("Information:" + element.Information);
certificateDetail.AppendLine(". . .");
certificateDetail.AppendLine(element.Certificate.ToString(true));
certificateDetail.AppendLine();
}
certificateDetail.AppendLine();
sLines = certificateDetail.ToString();
LogCertificateInfo(ref sLines);
}
}
}