Client to Send Soap Request and Receive Response

Client to send SOAP request and receive response

I normally use another way to do the same

using System.Xml;
using System.Net;
using System.IO;

public static void CallWebService()
{
var _url = "http://xxxxxxxxx/Service1.asmx";
var _action = "http://xxxxxxxx/Service1.asmx?op=HelloWorld";

XmlDocument soapEnvelopeXml = CreateSoapEnvelope();
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();
}
Console.Write(soapResult);
}
}

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

private static XmlDocument CreateSoapEnvelope()
{
XmlDocument soapEnvelopeDocument = new XmlDocument();
soapEnvelopeDocument.LoadXml(
@"<SOAP-ENV:Envelope xmlns:SOAP-ENV=""http://schemas.xmlsoap.org/soap/envelope/""
xmlns:xsi=""http://www.w3.org/1999/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/1999/XMLSchema"">
<SOAP-ENV:Body>
<HelloWorld xmlns=""http://tempuri.org/""
SOAP-ENV:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
<int1 xsi:type=""xsd:integer"">12</int1>
<int2 xsi:type=""xsd:integer"">32</int2>
</HelloWorld>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>");
return soapEnvelopeDocument;
}

private static void InsertSoapEnvelopeIntoWebRequest(XmlDocument soapEnvelopeXml, HttpWebRequest webRequest)
{
using (Stream stream = webRequest.GetRequestStream())
{
soapEnvelopeXml.Save(stream);
}
}

Client to send SOAP request to web service

Use for adding headers:

webRequest.Headers["HeaderToken"] = "HeaderValue";

And for GetRequestStream use:

using (var stream = await Task.Factory.FromAsync<Stream>(webRequest.BeginGetRequestStream, webRequest.EndGetRequestStream, null))
{
reqBody.Save(stream);
}

SOAP Client using C#

You can use a couple different system libraries to create an HttpWebRequest, create custom xml, and insert that xml into your request before you send it. (system.net, system.xml.linq, system.io)

I was able to hit your web service but got a 500 error. (Hopefully you see a log or that wasn't yours!)

Here's a simple class that can call a SOAP web service. It's almost the xml that you need but might need some tweaking. If you're having issues making custom xml this may be a possible solution.

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml.Linq;

namespace ConsoleApp1
{
/// <summary>
/// Random class
/// </summary>
class Class1
{
/// <summary>
/// Function that calls a SOAP web service
/// </summary>
public void CallSOAP()
{
try
{
// Construct http post request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("https://test-submit.health263.systems:8081/apacewebservices/AMF1_0"));
request.Method = "POST";
request.ContentType = "application/xml";
request.Accept = "application/xml";

// Setting a namespace for your xml
// I'm not sure the timezone one is set up properly
XNamespace soap = "urn:apace:member:format:1.0";
XNamespace timezone = "TimeZone=\"Africa/Johannesburg\"";

// This constructs your xml using the LINQ library. I modeled after your demo, but needs tweaking as I get 500 error from server.
XElement requestXML =
new XElement(soap + "Member",
new XElement("Request",
new XElement("Transaction",
new XElement("VersionNumber", "1.0"),
new XElement("Number", "434252 - 342234 - 6765"),
new XElement("SystemIdentifier", "SYSTEM999"),
new XElement("DestinationCode", "APACE"),
new XElement("ClientCountryCode", "ZA"),
new XElement(timezone + "Timestamp", "20160601123456"),
new XElement("TestIndicator", "Y"),
new XElement("User", "ProviderX/Jane Doe")
),
new XElement("MembershipLookup",
new XElement("Funder", "AFunder"),
new XElement("WithMembershipNumber",
new XElement("MembershipNumber", 123456789)
)
)
)
);

// Convert the xml into a stream that we write to our request
byte[] bytes = Encoding.UTF8.GetBytes(requestXML.ToString());
request.ContentLength = bytes.Length;
using (Stream putStream = request.GetRequestStream())
{
putStream.Write(bytes, 0, bytes.Length);
}

// Execute the request and get an xml response "reader". You can read all xml at once or line by line
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{

var streamData = reader.ReadToEnd();

}
}
catch (Exception ex)
{
// Write exception to console & wait for key press
Console.WriteLine(ex.Message + ex.StackTrace);
Console.ReadKey();
}
}
}
}

Correlation between request and response when sending soap message

I found out that is exactly what correlationState argument is used for in AfterReceiveReply method. Everything is described here:
https://docs.microsoft.com/en-us/dotnet/framework/wcf/samples/message-inspectors

Is there an easy way to get the soap message for the request and the soap message for the response, when invoking the web service?

Based on the MSDN article WCF Extensibility – Message Inspectors from Carlos Figueira one option is using a MessageInspector.

Create an class that implements an EndBehavior and ClientMessageInspector. Buffer the request and reply message so you can use them later on. In this example I print them in the Console.

Here is the combined implementation:

// using System.ServiceModel.Description;
// using System.ServiceModel.Dispatcher;
// using System.ServiceModel.Channels;
// using System.ServiceModel
public class InspectBehaviorAndnspector : IEndpointBehavior, IClientMessageInspector
{

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this);
}

public MessageBuffer RequestBuffer;
public MessageBuffer ReplyBuffer;

public void AfterReceiveReply(ref Message reply, object correlationState){
// messages are read only
ReplyBuffer = reply.CreateBufferedCopy(2048);
// so recreate the message after it was buffered
reply = ReplyBuffer.CreateMessage();
}

public object BeforeSendRequest(ref Message request, IClientChannel channel){
// messages are read only
RequestBuffer = request.CreateBufferedCopy(2048);
// so recreate the message after it was buffered
request = RequestBuffer.CreateMessage();
return "42";
}

// not needed for client
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}

public void Validate(ServiceEndpoint sep)
{
}
public void AddBindingParameters(ServiceEndpoint sep, BindingParameterCollection bpc)
{
}
}

Now on your client instance, assuming it derives from ClientBase, you can do:

var inspector = new InspectBehaviorAndnspector();
client.Endpoint.Behaviors.Add(inspector);

// this is your call
var response = client.invoke(parameter);

// either do a ToString
Console.WriteLine(inspector.RequestBuffer.CreateMessage().ToString());
// or Write it with XmlWriter
var sb = new StringBuilder();
using(var xw = XmlWriter.Create(sb, new XmlWriterSettings {Indent =true})) {
inspector.ReplyBuffer.CreateMessage().WriteMessage(xw);
}
Console.WriteLine(sb.ToString());

I have run this on an example with an Add service and this was my result:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/ISelfHostTest/Add</Action>
</s:Header>
<s:Body>
<Add xmlns="http://tempuri.org/">
<x>3</x>
<y>2</y>
</Add>
</s:Body>
</s:Envelope>

<?xml version="1.0" encoding="utf-16"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<AddResponse xmlns="http://tempuri.org/">
<AddResult>5</AddResult>
</AddResponse>
</s:Body>
</s:Envelope>

How to send data as a SoapMessage and get a reply?

Here is sample C# Console client and server code (they are in the same sample but this is only for demo purpose, of course) that uses HTTPS.

For the client side, we reuse the SoapHttpClientProtocol class, but for the server side, unfortunately, we cannot reuse anything because classes are completely tied to ASP.NET's (IIS) HttpContext class

For the server side, we use HttpListener, so, depending on your configuration, the server side will probably require admin rights to be able to call HttpListener's Prefixes.Add(url).

The code doesn't uses client certificate, but you can add this where I placed // TODO comments

The code assumes there is a certificate associated with the url and port used. If there's not (use netsh http show sslcert to dump all associated certs), you can use the procedure described here to add one: https://stackoverflow.com/a/11457719/403671

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml;

namespace SoapTests
{
class Program
{
static void Main(string[] args)
{
// code presumes there is an sslcert associated with the url/port below
var url = "https://127.0.0.1:443/";
using (var server = new MyServer(url, MyClient.NamespaceUri))
{
server.Start(); // requests will occur on other threads
using (var client = new MyClient())
{
client.Url = url;
Console.WriteLine(client.SendTextAsync("hello world").Result);
}
}
}
}

[WebServiceBinding(Namespace = NamespaceUri)]
public class MyClient : SoapHttpClientProtocol
{
public const string NamespaceUri = "http://myclient.org/";

public async Task<string> SendTextAsync(string text)
{
// TODO: add client certificates using this.ClientCertificates property
var result = await InvokeAsync(nameof(SendText), new object[] { text }).ConfigureAwait(false);
return result?[0]?.ToString();
}

// using this method is not recommended, as async is preferred
// but we need it with this attribute to make underlying implementation happy
[SoapDocumentMethod]
public string SendText(string text) => SendTextAsync(text).Result;

// this is the new Task-based async model (TAP) wrapping the old Async programming model (APM)
public Task<object[]> InvokeAsync(string methodName, object[] input, object state = null)
{
if (methodName == null)
throw new ArgumentNullException(nameof(methodName));

return Task<object[]>.Factory.FromAsync(
beginMethod: (i, c, o) => BeginInvoke(methodName, i, c, o),
endMethod: EndInvoke,
arg1: input,
state: state);
}
}

// server implementation
public class MyServer : TinySoapServer
{
public MyServer(string url, string namespaceUri)
: base(url)
{
if (namespaceUri == null)
throw new ArgumentNullException(nameof(namespaceUri));

NamespaceUri = namespaceUri;
}

// must be same as client namespace in attribute
public override string NamespaceUri { get; }

protected override bool HandleSoapMethod(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement)
{
switch (requestMethodElement.LocalName)
{
case "SendText":
// get the input
var text = requestMethodElement["text", NamespaceUri]?.InnerText;
text += " from server";

AddSoapResult(outputDocument, requestMethodElement, responseMethodElement, text);
return true;
}
return false;
}
}

// simple generic SOAP server
public abstract class TinySoapServer : IDisposable
{
private readonly HttpListener _listener;

protected TinySoapServer(string url)
{
if (url == null)
throw new ArgumentNullException(nameof(url));

_listener = new HttpListener();
_listener.Prefixes.Add(url); // this requires some rights if not used on localhost
}

public abstract string NamespaceUri { get; }
protected abstract bool HandleSoapMethod(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement);

public async void Start()
{
_listener.Start();
do
{
var ctx = await _listener.GetContextAsync().ConfigureAwait(false);
ProcessRequest(ctx);
}
while (true);
}

protected virtual void ProcessRequest(HttpListenerContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));

// TODO: add a call to context.Request.GetClientCertificate() to validate client cert
using (var stream = context.Response.OutputStream)
{
ProcessSoapRequest(context, stream);
}
}

protected virtual void AddSoapResult(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement, string innerText)
{
if (outputDocument == null)
throw new ArgumentNullException(nameof(outputDocument));

if (requestMethodElement == null)
throw new ArgumentNullException(nameof(requestMethodElement));

if (responseMethodElement == null)
throw new ArgumentNullException(nameof(responseMethodElement));

var result = outputDocument.CreateElement(requestMethodElement.LocalName + "Result", NamespaceUri);
responseMethodElement.AppendChild(result);
result.InnerText = innerText ?? string.Empty;
}

protected virtual void ProcessSoapRequest(HttpListenerContext context, Stream outputStream)
{
// parse input
var input = new XmlDocument();
input.Load(context.Request.InputStream);

var ns = new XmlNamespaceManager(new NameTable());
const string soapNsUri = "http://schemas.xmlsoap.org/soap/envelope/";
ns.AddNamespace("soap", soapNsUri);
ns.AddNamespace("x", NamespaceUri);

// prepare output
var output = new XmlDocument();
output.LoadXml("<Envelope xmlns='" + soapNsUri + "'><Body/></Envelope>");
var body = output.SelectSingleNode("//soap:Body", ns);

// get the method name, select the first node in our custom namespace
bool handled = false;
if (input.SelectSingleNode("//x:*", ns) is XmlElement requestElement)
{
var responseElement = output.CreateElement(requestElement.LocalName + "Response", NamespaceUri);
body.AppendChild(responseElement);

if (HandleSoapMethod(output, requestElement, responseElement))
{
context.Response.ContentType = "application/soap+xml; charset=utf-8";
context.Response.StatusCode = (int)HttpStatusCode.OK;
var writer = new XmlTextWriter(outputStream, Encoding.UTF8);
output.WriteTo(writer);
writer.Flush();
handled = true;
}
}

if (!handled)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
}

public void Stop() => _listener.Stop();
public virtual void Dispose() => _listener.Close();
}
}


Related Topics



Leave a reply



Submit