Wednesday, May 24, 2006

Making raw web service calls with the HttpWebRequest class

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));
    }

39 comments:

Goldie Satpal said...

Very useful post!! thx a lot!

I 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.

Anonymous said...

HI Mike,

I 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.

Mike Hadlow said...

Hi, looking at your code (and mine), it looks like my html encoding wasn't 100% effective (woops) The line:

sb.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

Anonymous said...

Hi Mike,

thanks 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

Anonymous said...

Hi Mike,

the 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

酷鱼 said...

非常感谢,为了这个我快要疯了,终于搞定了。

酷鱼 said...

非常感谢

Anonymous said...

Hi Mike,

I 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

Unknown said...

Hi Mike,

i 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

Mike Hadlow said...

Hi Phani (great name :)

If 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

Anonymous said...

Hi Mike!

Thank 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!

Anonymous said...

This article rocks!

Ryders said...

Mate, great piece!

I'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.

Mike Hadlow said...

Hi Seb,

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. 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.

Ryders said...

Awesome, that's worked the treat. I didn't know the exception would contain the full response - I should've thought about it, yeh!

The 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.

Anonymous said...

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.
Steve - Brighton, lovely spot in the World, almost as good as Middle park, Vic Australia

Mike Hadlow said...

Thanks Steve. I spent a year in Sydney back in 2000. I love Oz. We almost moved dowm permanently.

Unknown said...

I 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!

If you ever happen to be in Toronto, Canada, I am paying for the beer!

Mike Hadlow said...

Moacir,

That's very kind. Glad to be of service :)

James Poulose said...

Mike,
I 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

Mike Hadlow said...

Hi James,

Yes, 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.

Anonymous said...

You are ausome.. thanks a lot... I wanted this thing for a long time...

Nguyễn Quý Hòa said...

Thanks Mike.
It's what i am looking for.

Anonymous said...

I know it's a long shot as this blog post is quite old now but I could really do with some help!

I 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

Mike Hadlow said...

Hi Anonymous,

If 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!

jibran said...

Hi Mike,

I 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.

Mike Hadlow said...

Hi Jibran,

Unfortunately 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 :(

Unknown said...

Hi sir,

I 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

Vinay said...

Hi Kunal,

This 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.

justin said...

Thanks, Mike! I like your modular style of assembling this request.

@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.

Unknown said...

Thanks Mike,

your 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.

Unknown said...

Hi Mike,
I 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.

Mike Hadlow said...

Hi Hemant,

Because 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.

Unknown said...

Thanks Mike for your quick response.

Anonymous said...

Another 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.
http://techmentis.blogspot.com/2011/05/dynamic-web-service-invoker.html

Anonymous said...

Saved me a lot of work today.
Thanks for sharing.

Kalyan

Anonymous said...

Hi,

How can i get data from .ashx using httpwebrequest?

pls help.

karan patel said...

Hi mike
I 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);
}
}
}

Madchen said...

Hi Mike,

I 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