Generally, when writing code to communicate with web services in C#, the usual process is to add a service reference. Visual Studio then automatically creates all the wrapper classes/objects for you that you need in order to talk with the service. For the most part, this approach is great and really increases productivity because all the required web service “plumbing” is handled for you by the IDE. However, sometimes, you want greater control.
This article explains and gives you the code needed to work with a regular SOAP based web service without adding a reference.
But first, why would anyone want to give themselves more work?
Why You May Not Want A Web Service Reference
Sometimes, you would prefer to manually write all the code needed for authentication, method invoking, etc. An example of such a situation would be if you want your code to decide on which web service endpoint to connect to at runtime.
You could have slightly different copies of the web service on multiple servers (development, production, QA servers). And depending on the value of some variable defined in an external system (say SharePoint), your C# code then needs to dynamically point to the correct web service endpoint.
Another scenario when you may not want a reference is, if for some reason both the actual web service as well as the code to consume the service are in development at the same time.
So technically speaking, you don’t really have a web service (yet) since the production copy of the service has not been deployed.
While it is possible to dynamically change the url of a service reference like this…
var service = new MyService.MyWSSoapClient(); service.Endpoint.Address = new System.ServiceModel.EndpointAddress("http://localhost:8080/");
I have found that it is actually just more convenient, in some cases, to just work with the web service without adding a reference.
Now, let’s discuss the “how” part.
The “How” Part: Web Service Wrapper
Create a web service wrapper class like this:
public class MyWebService { public string Url { get; set; } public string MethodName { get; set; } public string UserName { get; set; } public string Password { get; set; } public string SOAPAction { get; set; } public string WSSPasswordType { get; set; } public Dictionary<string, string> Params = new Dictionary<string, string>(); public XDocument ResultXML; public string ResultString; public MyWebService() { } public MyWebService(string url, string methodName, string userName, string password, string soapAction, string wssPasswordType) { Url = url; MethodName = methodName; UserName = userName; Password = password; SOAPAction = soapAction; WSSPasswordType = wssPasswordType; } /// <summary> /// Invokes service /// </summary> public void Invoke() { Invoke(true); } /// <summary> /// Invokes service /// </summary> /// <param name="encode">Added parameters will encode? (default: true)</param> public void Invoke(bool encode) { string phrase = Guid.NewGuid().ToString(); string tempPhrase = phrase.Replace("-", ""); tempPhrase = tempPhrase.ToUpper(); string userNameToken = "UsernameToken-" + tempPhrase; DateTime created = DateTime.Now; string createdStr = created.ToString("yyyy-MM-ddThh:mm:ss.fffZ"); SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider(); byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase)); string nonce = Convert.ToBase64String(hashedDataBytes); string soapStr = ""; if (WSSPasswordType == "PasswordText") { soapStr = @"<?xml version=""1.0"" encoding=""utf-8""?> <soap:Envelope xmlns:soap=""http://www.w3.org/2003/05/soap-envelope""> <soap:Header> <wsse:Security soap:mustUnderstand=""true"" xmlns:wsse=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"" xmlns:wsu=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd""> <wsse:UsernameToken wsu:Id="""; soapStr += userNameToken; soapStr += @"""> <wsse:Username>" + UserName + @"</wsse:Username> <wsse:Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"">" + Password + @"</wsse:Password> <wsse:Nonce EncodingType=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"">" + nonce + @"</wsse:Nonce> <wsu:Created>" + createdStr + @"</wsu:Created> </wsse:UsernameToken> </wsse:Security> </soap:Header> <soap:Body> <{0}> {1} </{0}> </soap:Body> </soap:Envelope>"; } else if (WSSPasswordType == "None") { soapStr = @"<?xml version=""1.0"" encoding=""utf-8""?> <soap:Envelope xmlns:soap=""http://www.w3.org/2003/05/soap-envelope""> <soap:Body> <{0}> {1} </{0}> </soap:Body> </soap:Envelope>"; } HttpWebRequest req = (HttpWebRequest)WebRequest.Create(Url); req.Headers.Add("SOAPAction", SOAPAction); req.ContentType = "application/soap+xml;charset=\"utf-8\""; req.Accept = "application/soap+xml"; req.Method = "POST"; if (WSSPasswordType == "None") { NetworkCredential netCredential = new NetworkCredential(UserName, Password); byte[] credentialBuffer = new UTF8Encoding().GetBytes(UserName + ":" + Password); string auth = Convert.ToBase64String(credentialBuffer); req.Headers.Add("Authorization", "Basic " + auth); } ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback( delegate ( object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }); using (Stream stm = req.GetRequestStream()) { string postValues = ""; foreach (var param in Params) { if (encode) { postValues += string.Format("<{0}>{1}</{0}>", HttpUtility.UrlEncode(param.Key), HttpUtility.UrlEncode(param.Value)); } else { postValues += string.Format("<{0}>{1}</{0}>", param.Key, param.Value); } } soapStr = string.Format(soapStr, MethodName, postValues); using (StreamWriter stmw = new StreamWriter(stm)) { stmw.Write(soapStr); } } using (StreamReader responseReader = new StreamReader(req.GetResponse().GetResponseStream())) { string result = responseReader.ReadToEnd(); ResultXML = XDocument.Parse(result); ResultString = result; } } }
Inputs, Variables
Notice the soapStr variable above. The text/format of this variable may depend on your actual web service. So while the above sample code will very likely work for your setup, it may be a good idea to build your SOAP request using SOAP UI first and then grab the XML that SOAP UI generates.
The above code allows for situations where the value of WSSPasswordType is either “PasswordText” or “None”. This value will be set when calling the web service (see below). Again, the value you choose will depend on how your web service has been set up.
SOAP UI also exposes the “WSS-Password Type” variable in the request properties section. So don’t forget to set that correctly if you’re building your SOAP request string using SOAP UI.
The MethodName and SOAPAction inputs are related but again, their exact values depend on how your web service has been set up. As the name implies, MethodName is the name of the remote method you want to call. SOAPAction usually includes the method name at the end. But I think the best way to grab the exact value for SOAPAction is by looking into the WSDL of your web service (or by looking at the RAW request generated by SOAP UI after a successful call).
For example, the web service I was playing with while writing this article exposed the following:
- MethodName: sapUserCreate
- SOAPAction: SAPUserControl_Provider_WS_sapUserControl_Binder_sapUserCreate
…I was interfacing SharePoint with SAP modules using K2 and C#. Sweet stuff 🙂
The “How” Part: Invoking The Service
This part is simple. Just call the web service like this:
MyWebService mws = new MyWebService("webServiceUrl", "methodToCall", "webServiceUserName", "webServicePassword", "SOAPAction", "PasswordText"); mws.Params.Add("param1", "value_1"); mws.Params.Add("param2", "value_2"); mws.Invoke();
Add as many parameters as exposed/required by your web service.
Now you can view the result string or the result XML using:
string myResultString = mws.ResultString; XDocument myResultXml = mws.ResultXML;
Hope this helps someone.
If you know a more elegant way to call SOAP based web services without adding a reference using C#, don’t hesitate to contribute by adding your comments below.
Soap call gives 500 (internal server error)
how to do resolved it???
thank you so much, its help me a lot