If you are a well-seasoned ASP.NET Web Service developer you most certainly know about the SoapClient class and how it can be used to send SOAP messages using transport-independent protocols using two main methods:
- The ASP.NET services, which is old way of doing SOA
- The WCF framework, which is the "latest" and "newest" way to do that.
We already talked about these approaches in a couple posts some months ago (read here and here for further info). However, there could be some edge-case scenarios where you want (or need) to call a SOAP-based web service without using the WSDL and/or the Add Service Reference Visual Studio feature, maybe because you can't possibly put the code bloat it produces into your code base.
When such need arises, the following helper class might be precisely what you're looking for:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Xml; namespace Ryadel.Web.SOAP { /// <summary> /// Helper class to send custom SOAP requests. /// </summary> public static class SOAPHelper { /// <summary> /// Sends a custom sync SOAP request to given URL and receive a request /// </summary> /// <param name="url">The WebService endpoint URL</param> /// <param name="action">The WebService action name</param> /// <param name="parameters">A dictionary containing the parameters in a key-value fashion</param> /// <param name="soapAction">The SOAPAction value, as specified in the Web Service's WSDL (or NULL to use the url parameter)</param> /// <param name="useSOAP12">Set this to TRUE to use the SOAP v1.2 protocol, FALSE to use the SOAP v1.1 (default)</param> /// <returns>A string containing the raw Web Service response</returns> public static string SendSOAPRequest(string url, string action, Dictionary<string, string> parameters, string soapAction = null, bool useSOAP12 = false) { // Create the SOAP envelope XmlDocument soapEnvelopeXml = new XmlDocument(); var xmlStr = (useSOAP12) ? @"<?xml version=""1.0"" encoding=""utf-8""?> <soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope""> <soap12:Body> <{0} xmlns=""{1}"">{2}</{0}> </soap12:Body> </soap12:Envelope>" : @"<?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> <{0} xmlns=""{1}"">{2}</{0}> </soap:Body> </soap:Envelope>"; string parms = string.Join(string.Empty, parameters.Select(kv => String.Format("<{0}>{1}</{0}>", kv.Key, kv.Value)).ToArray()); var s = String.Format(xmlStr, action, new Uri(url).GetLeftPart(UriPartial.Authority) + "/", parms); soapEnvelopeXml.LoadXml(s); // Create the web request HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url); webRequest.Headers.Add("SOAPAction", soapAction ?? url); webRequest.ContentType = (useSOAP12) ? "application/soap+xml;charset=\"utf-8\"" : "text/xml;charset=\"utf-8\""; webRequest.Accept = (useSOAP12) ? "application/soap+xml" : "text/xml"; webRequest.Method = "POST"; // Insert SOAP envelope using (Stream stream = webRequest.GetRequestStream()) { soapEnvelopeXml.Save(stream); } // Send request and retrieve result string result; using (WebResponse response = webRequest.GetResponse()) { using (StreamReader rd = new StreamReader(response.GetResponseStream())) { result = rd.ReadToEnd(); } } return result; } } } |
As you can see, the above code can be used to issue a manual SOAP-based request using a standard WebRequest C# class: to emphasize the fact that this is an home-cooked, emergency-level code, we also chose to go with manually (un)typed XML blocks, avoiding XMLDocument, XmlWriter, System.Xml and other C# native approaches.
Here's how you can use it in your ASP.NET / ASP.NET Core project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var url = "https://mywebservice.com/Service.asmx?op=MyActionMethod"; var action = "GetGeoLocationData"; var soapAction = "https://mywebservice.com/MyActionMethod"; var parms = new Dictionary<string, string>(); parms.Add("token", "the required auth token"); parms.Add("ws_param_1", "web service parameter 1"); parms.Add("ws_param_2", "web service parameter 2"); string result = SOAPHelper.SendSOAPRequest(url, action, parms, soapAction, false); |
As we can see, we're issuing a sample request to an ASMX web service, which has a WSDL that will require a specific soapAction property value: whenever the SOAP Web Service you're connecting to requires a SOAPAction equals to the endpoint URL, you can leave that property set to null. When in doubt, take a look at the WSDL and act accordingly.
That's it for now: happy coding!
Re How to perform a SOAP Web Service Request in ASP.NET C# without using WSDL, proxy classes or SoapClient
This is very useful but I’m looking for a solution which works for SOAP 1.2 with WCF (using .svc). I have played around with your solution but without success.
I would appreciate any guidance.
Hi, this is very useful, thanks a lot. Can you tell how can i insert another variable in a higher section?
Something like this:
soap:Body
tem:Section1
tem:token
INSERT ANOTHER VARIABLE HERE
/tem:token
tem:Section2
{0} xmlns=””{1}””>{2}</{0}
/tem:Section2
/tem:Section1
/soap:Body
Just add another Dictionary parameter to the SendSOAPRequest method (call it “tokenParameters” or something like that) and replicate the same string.Format behavior: 1 dictionary for the tem:token attribute contents, and a 2nd dictionary for the tem:Section2 contents.