Calling a Web Method in C# without Service Reference
Last week I gave you a method that, using SOAP and HttpWebRequest, allowed you to invoke a Web Method without a WSDL or a Web Reference. Well today, I’ll give you an improved version of that method. In fact I’ll give you a whole ready-to-use class with additional functionality.
Recapping, if you need to call a web method but fail to have a WSDL or can’t use a Web or Service Reference because you want your code to be dynamic, then SOAP and HttpWebRequests are the way to go. You can then create a Proxy having the same API (methods, inputs, outputs) as the WebService you’re trying to call. For instance:
internal class ExampleAPIProxy
{
private static WebService ExampleAPI = new WebService("http://.../example.asmx"); // DEFAULT location of the WebService, containing the WebMethods
public static void ChangeUrl(string webserviceEndpoint)
{
ExampleAPI = new WebService(webserviceEndpoint);
}
public static string ExampleWebMethod(string name, int number)
{
ExampleAPI.PreInvoke();
ExampleAPI.AddParameter("name", name); // Case Sensitive! To avoid typos, just copy the WebMethod's signature and paste it
ExampleAPI.AddParameter("number", number.ToString()); // all parameters are passed as strings
try
{
ExampleAPI.Invoke("ExampleWebMethod"); // name of the WebMethod to call (Case Sentitive again!)
}
finally { ExampleAPI.PosInvoke(); }
return ExampleAPI.ResultString; // you can either return a string or an XML, your choice
}
}
For that to work you’ll need the WebService class.
/// <summary>
/// This class is an alternative when you can't use Service References. It allows you to invoke Web Methods on a given Web Service URL.
/// Based on the code from http://stackoverflow.com/questions/9482773/web-service-without-adding-a-reference
/// </summary>
public class WebService
{
public string Url { get; private set; }
public string Method { get; private set; }
public Dictionary<string, string> Params = new Dictionary<string, string>();
public XDocument ResponseSOAP = XDocument.Parse("<root/>");
public XDocument ResultXML = XDocument.Parse("<root/>");
public string ResultString = String.Empty;
private Cursor InitialCursorState;
public WebService()
{
Url = String.Empty;
Method = String.Empty;
}
public WebService(string baseUrl)
{
Url = baseUrl;
Method = String.Empty;
}
public WebService(string baseUrl, string methodName)
{
Url = baseUrl;
Method = methodName;
}
// Public API
/// <summary>
/// Adds a parameter to the WebMethod invocation.
/// </summary>
/// <param name="name">Name of the WebMethod parameter (case sensitive)</param>
/// <param name="value">Value to pass to the paramenter</param>
public void AddParameter(string name, string value)
{
Params.Add(name, value);
}
public void Invoke()
{
Invoke(Method, true);
}
/// <summary>
/// Using the base url, invokes the WebMethod with the given name
/// </summary>
/// <param name="methodName">Web Method name</param>
public void Invoke(string methodName)
{
Invoke(methodName, true);
}
/// <summary>
/// Cleans all internal data used in the last invocation, except the WebService's URL.
/// This avoids creating a new WebService object when the URL you want to use is the same.
/// </summary>
public void CleanLastInvoke()
{
ResponseSOAP = ResultXML = null;
ResultString = Method = String.Empty;
Params = new Dictionary<string, string>();
}
#region Helper Methods
/// <summary>
/// Checks if the WebService's URL and the WebMethod's name are valid. If not, throws ArgumentNullException.
/// </summary>
/// <param name="methodName">Web Method name (optional)</param>
private void AssertCanInvoke(string methodName = "")
{
if (Url == String.Empty)
throw new ArgumentNullException("You tried to invoke a webservice without specifying the WebService's URL.");
if ((methodName == "") && (Method == String.Empty))
throw new ArgumentNullException("You tried to invoke a webservice without specifying the WebMethod.");
}
private void ExtractResult(string methodName)
{
// Selects just the elements with namespace http://tempuri.org/ (i.e. ignores SOAP namespace)
XmlNamespaceManager namespMan = new XmlNamespaceManager(new NameTable());
namespMan.AddNamespace("foo", "http://tempuri.org/");
XElement webMethodResult = ResponseSOAP.XPathSelectElement("//foo:" + methodName + "Result", namespMan);
// If the result is an XML, return it and convert it to string
if (webMethodResult.FirstNode.NodeType == XmlNodeType.Element)
{
ResultXML = XDocument.Parse(webMethodResult.FirstNode.ToString());
ResultXML = Utils.RemoveNamespaces(ResultXML);
ResultString = ResultXML.ToString();
}
// If the result is a string, return it and convert it to XML (creating a root node to wrap the result)
else
{
ResultString = webMethodResult.FirstNode.ToString();
ResultXML = XDocument.Parse("<root>" + ResultString + "</root>");
}
}
/// <summary>
/// Invokes a Web Method, with its parameters encoded or not.
/// </summary>
/// <param name="methodName">Name of the web method you want to call (case sensitive)</param>
/// <param name="encode">Do you want to encode your parameters? (default: true)</param>
private void Invoke(string methodName, bool encode)
{
AssertCanInvoke(methodName);
string soapStr =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema""
xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
<soap:Body>
<{0} xmlns=""http://tempuri.org/"">
{1}
</{0}>
</soap:Body>
</soap:Envelope>";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(Url);
req.Headers.Add("SOAPAction", "\"http://tempuri.org/" + methodName + "\"");
req.ContentType = "text/xml;charset=\"utf-8\"";
req.Accept = "text/xml";
req.Method = "POST";
using (Stream stm = req.GetRequestStream())
{
string postValues = "";
foreach (var param in Params)
{
if (encode) postValues += string.Format("<{0}>{1}</{0}>", HttpUtility.HtmlEncode(param.Key), HttpUtility.HtmlEncode(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();
ResponseSOAP = XDocument.Parse(Utils.UnescapeString(result));
ExtractResult(methodName);
}
}
/// <summary>
/// This method should be called before each Invoke().
/// </summary>
internal void PreInvoke()
{
CleanLastInvoke();
InitialCursorState = Cursor.Current;
Cursor.Current = Cursor.WaitCursor;
// feel free to add more instructions to this method
}
/// <summary>
/// This method should be called after each (successful or unsuccessful) Invoke().
/// </summary>
internal void PosInvoke()
{
Cursor.Current = InitialCursorState;
// feel free to add more instructions to this method
}
#endregion
}
And lastly the Utils class:
public static class Utils
{
/// <summary>
/// Remove all xmlns:* instances from the passed XmlDocument to simplify our xpath expressions
/// </summary>
public static XDocument RemoveNamespaces(XDocument oldXml)
{
// FROM: http://social.msdn.microsoft.com/Forums/en-US/bed57335-827a-4731-b6da-a7636ac29f21/xdocument-remove-namespace?forum=linqprojectgeneral
try
{
XDocument newXml = XDocument.Parse(Regex.Replace(
oldXml.ToString(),
@"(xmlns:?[^=]*=[""][^""]*[""])",
"",
RegexOptions.IgnoreCase | RegexOptions.Multiline)
);
return newXml;
}
catch (XmlException error)
{
throw new XmlException(error.Message + " at Utils.RemoveNamespaces");
}
}
/// <summary>
/// Remove all xmlns:* instances from the passed XmlDocument to simplify our xpath expressions
/// </summary>
public static XDocument RemoveNamespaces(string oldXml)
{
XDocument newXml = XDocument.Parse(oldXml);
return RemoveNamespaces(newXml);
}
/// <summary>
/// Converts a string that has been HTML-enconded for HTTP transmission into a decoded string.
/// </summary>
/// <param name="escapedString">String to decode.</param>
/// <returns>Decoded (unescaped) string.</returns>
public static string UnescapeString(string escapedString)
{
return HttpUtility.HtmlDecode(escapedString);
}
} ⁂