Sometimes it's really nice to be able to make a raw call to a web service by manaully putting together your own SOAP envelope. The System.Net.HttpWebRequest class makes it really easy. There are a number of good reasons to do this. Maybe you want to call a web service without having to create a proxy class with wsdl.exe, or maybe you want to have more control over the creation of the SOAP envelope or you don't want to have to rely on the XmlSerializer. Maybe you want to create the xml by doing xslt transforms rather than serializing a .net type.
First you need to create the envelope xml, this function takes a string of xml data as the content and inserts it into a soap envelope.
Just open your web service in IE by typing the url into the Address bar to see what the structure of the soap:Body should be.
static string _soapEnvelope =
@"<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></soap:Body></soap:Envelope>";
private static XmlDocument CreateSoapEnvelope(string content)
{
StringBuilder sb = new StringBuilder(_soapEnvelope);
sb.Insert(sb.ToString().IndexOf("</soap:Body>"), content);
// create an empty soap envelope
XmlDocument soapEnvelopeXml = new XmlDocument();
soapEnvelopeXml.LoadXml(sb.ToString());
return soapEnvelopeXml;
}
Next you create the HttpWebRequest object. The url is the url of the .aspx file and the action is your namespace plus the web method, e.g: 'mikehadlow.com/adder'.
private static HttpWebRequest CreateWebRequest(string url, string action)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Headers.Add("SOAPAction", action);
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
webRequest.Accept = "text/xml";
webRequest.Method = "POST";
return webRequest;
}
Insert the envelope into the web request.
private static void InsertSoapEnvelopeIntoWebRequest(XmlDocument soapEnvelopeXml, HttpWebRequest webRequest)
{
using (Stream stream = webRequest.GetRequestStream())
{
soapEnvelopeXml.Save(stream);
}
}
Then you can call .GetResponse on the HttpWebRequest object to call the web service. You get the response back by getting the response stream with the GetResponseStream method of the webResponse object.
Here's it all put together:
static string _url = "http://mikehadlow.com/myService.asmx";
static string _action = "http://mikehadlow.com/myWebMethod";
static string _inputPath = @"C:\mike\Play\input.xml";
static string _outputPath = @"C:\mike\Play\output.xml";
static void Main(string[] args)
{
string content = File.ReadAllText(_inputPath);
XmlDocument soapEnvelopeXml = CreateSoapEnvelope(content);
HttpWebRequest webRequest = CreateWebRequest(_url, _action);
InsertSoapEnvelopeIntoWebRequest(soapEnvelopeXml, webRequest);
// begin async call to web request.
IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);
// suspend this thread until call is complete. You might want to
// do something usefull here like update your UI.
asyncResult.AsyncWaitHandle.WaitOne();
// get the response from the completed web request.
string soapResult;
using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
using (StreamReader rd = new StreamReader(webResponse.GetResponseStream()))
{
soapResult = rd.ReadToEnd();
}
File.WriteAllText(_outputPath, FormatDocument(soapResult));
}
Very useful post!! thx a lot!
ReplyDeleteI was looking to set some custom HTTP header while making a webservice call and was trying out few things. This was a great tool for debugging!
However, I ended up overriding the GetWebRequest method in the auto generated proxy the achieve the same.
HI Mike,
ReplyDeleteI am trying to invoke a web service in c# using .NET framework SDK v2.0 on windows 2000 professional.
[without using wsdl.exe]
[webservice has created in Oracle Jdeveloper]
I follwed your given instruction and created client programe. but i am getting these errors.
cs 1518, cs 1022
cs 1001, cs 1010
in WSDL descrition:
it has two methods -
1> testWebserviceXML
2> testWebserviceXMLRowSet
and namespace tag is like this
targetNamespace="http://dbconnection1/EpassWebService.wsdl"
i followed your instruction and created two xml files also for input soap:body xml and webservice output xml.
then wrote code like this:
[i tried to do it all together]
static string _url = "http://localhost/PL_SQL_WS-GetIdms-context-root/EpassWebServiceSoapHttpPort";
static string _action = "http://dbconnection1/EpassWebService.wsdl/testWebserviceXML";
static string _inputPath = @"C:\csharp\v2.0\epassweb\soapenv.xml";
static string _outputPath = @"C:\csharp\v2.0\epassweb\xmloutput.xml";
static void Main(string[] args)
{
string content = File.ReadAllText(_inputPath);
// XmlDocument soapEnvelopeXml = CreateSoapEnvelope(content);
StringBuilder sb = new StringBuilder(_soapEnvelope);
sb.Insert(sb.ToString().IndexOf(""), content);
// create an empty soap envelope
XmlDocument soapEnvelopeXml = new XmlDocument();
soapEnvelopeXml.LoadXml(sb.ToString());
// return soapEnvelopeXml;
//HttpWebRequest webRequest = CreateWebRequest(_url, _action);
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Headers.Add("SOAPAction", action);
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
webRequest.Accept = "text/xml";
webRequest.Method = "POST";
//return webRequest;
//InsertSoapEnvelopeIntoWebRequest(soapEnvelopeXml, webRequest);
using (Stream stream = webRequest.GetRequestStream())
{
soapEnvelopeXml.Save(stream);
}
// begin async call to web request.
IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);
// suspend this thread until call is complete. You might want to
// do something usefull here like update your UI.
asyncResult.AsyncWaitHandle.WaitOne();
// get the response from the completed web request.
string soapResult;
using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
using (StreamReader rd = new StreamReader(webResponse.GetResponseStream()))
{
soapResult = rd.ReadToEnd();
}
File.WriteAllText(_outputPath, FormatDocument(soapResult));
}
saved as webtest.cs, and while compiling i got these errors
cs 1518, cs 1022
cs 1001, cs 1010
any suggestions why i am getting these errors now?
Thank you.
Hi, looking at your code (and mine), it looks like my html encoding wasn't 100% effective (woops) The line:
ReplyDeletesb.Insert(sb.ToString().IndexOf(""), content);
Should read:
sb.Insert(sb.ToString().IndexOf("</soap:Body>"), content);
The orginal code would insert the body in the wrong place in the SOAP envelope. Try changing that line and see if it makes any difference.
Good luck!
Mike
Hi Mike,
ReplyDeletethanks for your quick reply.
those CS errors gone.
but now on running my exe i am getting this error.
Unhandled Exception: System.Xml.XmlException: There are multiple root elements.
Line 8, position 2.
so how can i remove this multiple root element.
can we remove this using any extra code in same client program
Thank you
Hi Mike,
ReplyDeletethe above error has gone. But now i am gettig this error on executing my EXE
System.Net.WebException: The remote server returned an error (400) Bad Request
any suggestion?
Thank you
非常感谢,为了这个我快要疯了,终于搞定了。
ReplyDelete非常感谢
ReplyDeleteHi Mike,
ReplyDeleteI have a httphandler(.ashx) file which processes the incoming httpwebRequest.
This httpwebRequest(web request is framed in a soap envelope) is passed to handler from another web service.
My question is how do i return the response from this handler back to the caller web service?
i tried doing this in handler(.ashx):-
string data = "true|false";
context.Response.Write(data);
And at the caller web service:-
using (WebResponse webResponse = webRequest.GetResponse()) using (StreamReader rd = new StreamReader(webResponse.GetResponseStream()))
{
sbResult.Append(rd.ReadToEnd());
}
However, i get the following error(checked in debug mode) at webRequest.GetResponse line as:-
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
Let me know if you can portion out your knowledge on this.
Plz leave ur comment here ASAP.
Thanks
Hi Mike,
ReplyDeletei want to invoke a webservice without taking webreference and proxy.In ur sample you are passing the xml file as input .what is that xml file contains ,can u give me the full description about that
can u say abt that plz
thank you
Hi Phani (great name :)
ReplyDeleteIf you don't understand what the XML is, you really should do a bit more research into web services and SOAP. This technique does require that you understand the raw XML that's exchanged when a web service is invoked. There are plenty of resources out there: This w3schools tutorial is a good starting place.
http://www.w3schools.com/soap/default.asp
Hi Mike!
ReplyDeleteThank you for a good article. It has helped me quite a lot, since I'm going to create a very basic automated generic web service test application. To make sure I would have to tie it to a specific web service I had to find a good way to do dynamic web service calls and this solves my problem to full extent!
Thanks again!
This article rocks!
ReplyDeleteMate, great piece!
ReplyDeleteI've tried it and love it! This said, I was wondering if you could enlight me on how to handle a SOAP exception...?!
Basically, WebResponse webResponse = httpRequest.EndGetResponse(asyncResult) will raise a null pointer exception and the httpRequest doesn't seem to have any clue on the exception itself...
any idea?
Cheers,
Seb.
Hi Seb,
ReplyDeleteYou have to catch any WebException that's thrown by GetResponse (or EndGetResponse) and then attempt to read its response property to examine the actual SOAP error that's come back from the service. Something like this:
try
{
using (WebResponse webResponse = webRequest.GetResponse())
{
return WriteResponseToFile(webResponse);
}
}
catch (WebException webException)
{
if (webException.Response == null) throw;
using (WebResponse webResponse = webException.Response)
{
return WriteResponseToFile(webResponse);
}
}
However, if you're getting a null pointer exception there might be something else wrong.
Awesome, that's worked the treat. I didn't know the exception would contain the full response - I should've thought about it, yeh!
ReplyDeleteThe null pointer exception was thrown by the GetResponseStream on a non existing 'response'.. so it's all good now! ;)
Thanks for your help! I've updated a few other post elsewhere to point back here! ;)
Cheers,
Seb.
Thanks Mike, Very useful; used your code to confirm my code wasn't wrong - then sorted the issue; but I like your cleaner code implementation.
ReplyDeleteSteve - Brighton, lovely spot in the World, almost as good as Middle park, Vic Australia
Thanks Steve. I spent a year in Sydney back in 2000. I love Oz. We almost moved dowm permanently.
ReplyDeleteI just wanted to say ... THANK YOU SO VERY MUCH!! It's 11:30PM here at the office and I have this deadline for tomorrow and you really saved the day!
ReplyDeleteIf you ever happen to be in Toronto, Canada, I am paying for the beer!
Moacir,
ReplyDeleteThat's very kind. Glad to be of service :)
Mike,
ReplyDeleteI have got a few queries.From the response XML how do i get my result objects back? Do i need to do a string search, then extract the xml part, and then de-serialize it? Is there any other way? Thanks for your article.
Regards,
James
Hi James,
ReplyDeleteYes, that's probably the best approach. However, one of the reasons you might want to do raw calls like this is because you don't know the shape of the message in advance. In that case you'd have to parse the XML manually.
You are ausome.. thanks a lot... I wanted this thing for a long time...
ReplyDeleteThanks Mike.
ReplyDeleteIt's what i am looking for.
I know it's a long shot as this blog post is quite old now but I could really do with some help!
ReplyDeleteI have followed your example but when I get to the line "using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))", I get the following error:
System.Net.WebException: The remote server returned an error: (500) Internal Server Error
Any advice you can give to get round this would be most welcomed!
Rob
Hi Anonymous,
ReplyDeleteIf you're getting a 500 error from the server, you'll need to investigate it from that end. It could be that the manually created request is malformed, which is causing the error, but the only way to find out is to debug the server (or check the server log, if it's writing one). Sorry not to be much help...
Good luck!
Hi Mike,
ReplyDeleteI was wondering if you could share the XML you are inserting into the soap envelope (ie. the 'content' variable from CreateSoapEnvelope(string content)).
I'm also getting 500 internal server error and I'm suspecting that my xml is malformed in some way. Or I'm sending incorrect information.
Thanks for the post.
Hi Jibran,
ReplyDeleteUnfortunately I wrote this post a while ago, and I no longer have this specific piece of code. In any case, the content is specific to the server, and my original purpose with this code was to eventually assemble the SOAP body automatically by parsing some WSDL. You can look at my posts on WsdlWorks, and the WsdlWorks project site (google it) if you want to learn more about this.
As I said before, this is a relatively advanced technique and it does assume that you have a full grasp of SOAP web services. I'm afraid you will need to be able to understand and debug any the interaction with the server yourself.
Sorry not to be any help :(
Hi sir,
ReplyDeleteI am calling a webservice in asp.net by http basic authentication(RFC2617) but unable to do so despite numerous attempts.
my code is(not working for me):
MyService service = new MyService();
service.Credentials = new NetworkCredential("username", "password");
service.PreAuthenticate = true;
and
MyService service = new MyService();
NetworkCredential netCredential = new NetworkCredential("username", "password");
Uri uri = new Uri(service.Url);
service.Credentials = netCredential.GetCredential(uri, "Basic");
service.PreAuthenticate = true;
My client has given me document as follows how to call webservice:
please read about RFC2617 and how to implement it.
It's clearly mentioned on page 110 of the Integration Document:
Technology Partner Authentication
"Technology Partner", in the context of this document, means an authorized
"Account" on the IRCTC site authorized to consume the web services. The username and password of the "Technology Partner" is set in the http header.
NOTE: IRCTC Webservices follows strictly, the HTTP BASIC Authentication
(RFC2617), so the same has to be followed by the integrating parties
whether on Java/.NET or any other platform.
i try to do what Mr. Mark written on his blog but it gives me error: does not find method getwebrequest to override .
http://mark.michaelis.net/Blog/CallingWebServicesUsingBasicAuthentication.aspx
plz sir help me.
Thanks & Regards
Kunal Tilak
Hi Kunal,
ReplyDeleteThis is Vinay, Bangalore.
your IRCTC problem (RFC 2617 Http Basic Authentication) solved or still it is open. If it is closed please post ur comment on this problem.
Thanks, Mike! I like your modular style of assembling this request.
ReplyDelete@jibran: The soap requests I have seen expect to see an XML header which this response does not include. You may add such a header by adding a couple of lines of code to the CreateSoapEnvelope method:
XmlNode declaration = soapEnvelopeXml.CreateNode(XmlNodeType.XmlDeclaration, null, null);
soapEnvelopeXml.AppendChild(declaration);
This resolved the issue for me.
Thanks Mike,
ReplyDeleteyour response:
You have to catch any WebException that's thrown by GetResponse (or EndGetResponse) and then attempt to read its response property to examine the actual SOAP error that's come back from the service.
Was very usefull. I didn't know that the WebException contained the error response.
Thanks.
Hi Mike,
ReplyDeleteI have written the bellow code to make a webservice call which is working fine.
try
{
resp_WebResponse = req_WebRequest.GetResponse();
//Get the response in the stream.
stm_RequestStream = resp_WebResponse.GetResponseStream();
sr_StreamReader = new StreamReader(stm_RequestStream);
ResponseXML = sr_StreamReader.ReadToEnd();
}
catch (WebException WebExceptionObj)
{
//code to handle web exception
}
Now I want to handle a SOAP exception so I have modified the above code as
try
{
resp_WebResponse = req_WebRequest.GetResponse();
//Get the response in the stream.
stm_RequestStream = resp_WebResponse.GetResponseStream();
sr_StreamReader = new StreamReader(stm_RequestStream);
ResponseXML = sr_StreamReader.ReadToEnd();
}
catch (SoapException SOAPExceptionObj)
{
//code to handle SOAP exception
}
catch (WebException WebExceptionObj)
{
//code to handle web exception
}
Problem here, it always go in web exception block eventhough web service I am calling throws SOAP exception.
Can you please help me how to handle SOAP exception.
Hi Hemant,
ReplyDeleteBecause you are calling your service with a lower level API (WebRequest) you will never see it throw a SOAPException. But you can catch the WebException and then examine the returned SOAP envelope to find the exception details.
Thanks Mike for your quick response.
ReplyDeleteAnother eficient option to do this same thing without manually playing with the SOAP text is to do some dynamic code gen. Really nice and efficient.
ReplyDeletehttp://techmentis.blogspot.com/2011/05/dynamic-web-service-invoker.html
Saved me a lot of work today.
ReplyDeleteThanks for sharing.
Kalyan
Hi,
ReplyDeleteHow can i get data from .ashx using httpwebrequest?
pls help.
Hi mike
ReplyDeleteI follwed your given instruction and created client programe. but i am getting these errors.
System.Xml.XmlException was unhandled by user code
Message=Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it. Line 5, position 14.
Source=System.Xml
LineNumber=5
LinePosition=14
SourceUri=""
I wrote the Code Like this
public partial class Test2 : System.Web.UI.Page
{
static string _url = "http://wstest.ezeego1.com/axis/services/wssearchhoteldetails7wsdl";
static string _action = "http://wstest.ezeego1.com/axis/services/wssearchhoteldetails7wsdl";
static string _inputPath = @"C:\Documents and Settings\karan\My Documents\Visual Studio 2010\WebSites\WebServiceTest\TestRequest.xml";
static string _outputPath = @"C:\Documents and Settings\karan\My Documents\Visual Studio 2010\WebSites\WebServiceTest\TestResponse.xml";
protected void Page_Load(object sender, EventArgs e)
{
string content = File.ReadAllText(_inputPath);
XmlDocument soapEnvelopeXml = CreateSoapEnvelope(content);
HttpWebRequest webRequest = CreateWebRequest(_url, _action);
InsertSoapEnvelopeIntoWebRequest(soapEnvelopeXml, webRequest);
// begin async call to web request.
IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);
// suspend this thread until call is complete. You might want to
// do something usefull here like update your UI.
asyncResult.AsyncWaitHandle.WaitOne();
// get the response from the completed web request.
string soapResult;
using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
using (StreamReader rd = new StreamReader(webResponse.GetResponseStream()))
{
soapResult = rd.ReadToEnd();
}
File.WriteAllText(_outputPath, FormatDocument(soapResult));
}
private string FormatDocument(string soapResult)
{
throw new NotImplementedException();
}
private static HttpWebRequest CreateWebRequest(string url, string action)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Headers.Add("SOAPAction", action);
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
webRequest.Accept = "text/xml";
webRequest.Method = "POST";
return webRequest;
}
static string _soapEnvelope =
@"
";
private static XmlDocument CreateSoapEnvelope(string content)
{
StringBuilder sb = new StringBuilder(_soapEnvelope);
sb.Insert(sb.ToString().IndexOf(""), content);
// create an empty soap envelope
XmlDocument soapEnvelopeXml = new XmlDocument();
soapEnvelopeXml.LoadXml(sb.ToString());
return soapEnvelopeXml;
}
private static void InsertSoapEnvelopeIntoWebRequest(XmlDocument soapEnvelopeXml, HttpWebRequest webRequest)
{
using (Stream stream = webRequest.GetRequestStream())
{
soapEnvelopeXml.Save(stream);
}
}
}
Hi Mike,
ReplyDeleteI need to consume a webservice with WS-Security Username Token Profile, like the one I attach here. Please can you give me an idea how to do this? I already have yours working. I read this link but I still need a hint please!. https://msdn.microsoft.com/en-us/library/ms996951.aspx
bm_bancopE+owsPjiTobZ6MTBtNvnsEXK3M=m/HDcmgIH4rSLKF8Jefa+Q==2015-11-17T01:01:11.293Z