Logging Response XML With WcfCoreMtomEncoder? - wcf

I'm writing a small .NET Core app that needs to call a web service which uses MTOM encoding. I found the WcfCoreMtomEncoder (https://www.nuget.org/packages/WcfCoreMtomEncoder/0.1.16) and it's working fine to encode/decode the requests and responses.
However, I need to be able to log the requests and responses. I added an IEndpointBehavior class to the endpoint to do the logging, which works fine for the request. Unfortunately for the Response the IEndpointBehavior is getting called before the MTOM message is actually decoded so the response is logged as such:
<?xml version="1.0" encoding="utf-16"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header />
<soapenv:Body>... stream ...</soapenv:Body>
</soapenv:Envelope>
I've seen suggestions to add logging to an encoder instead, but the constructor for CustomBinding takes a BindingElement and I'm not sure where I would add the new encoder. Here's the code I have now:
var textMessageEncoding = new TextMessageEncodingBindingElement();
textMessageEncoding.MessageVersion = MessageVersion.Soap11;
var mtomBindingElement = new WcfCoreMtomEncoder.MtomMessageEncoderBindingElement(textMessageEncoding);
var httpTransport = new HttpTransportBindingElement();
httpTransport.AuthenticationScheme = System.Net.AuthenticationSchemes.Basic;
var binding = new CustomBinding(mtomBindingElement, httpTransport);
var endpoint = new EndpointAddress(config["EndpointURL"]);
fntClient = new FileNetServicesImplClient(binding, endpoint);
fntClient.Endpoint.EndpointBehaviors.Add(new MessageLoggingBehavior(log));
So how can I log the Response after it's decoded by the MTOM Encoder? Is there a way to move the EndpointBehavior so it happens after the decoding?

Related

.NET HttpClient hangs after several requests (unless Fiddler is active)

I am using System.Net.Http.HttpClient to post a sequence of requests from a console application to a REST API and to deserialize the JSON responses into strongly-typed objects. My implementation is like this:
using (var client = new HttpClient())
{
var content = new StringContent(data, Encoding.UTF8, "text/html");
var response = client.PostAsync(url, content).Result;
response.EnsureSuccessStatusCode();
return response.Content.ReadAsAsync<MyClass>().Result;
}
However, I am experiencing a problem very similar to one described in this question, whereby everything works fine when the requests are routed via Fiddler, but it hangs after the 4th or 5th request when Fiddler is disabled.
If the cause of the problem is the same, I assume I need to do something more with HttpClient to get it to fully release its resources after each request but I am unable to find any code samples that show how to do this.
Hoping somebody can point me in the right direction.
Many thanks,
Tim
You are not disposing of the HttpResponseMessage object. This can leave open streams with the server, and after some quota of streams with an individual server is filled, no more requests will be sent.
using (var client = new HttpClient())
{
var content = new StringContent(data, Encoding.UTF8, "text/html");
using(var response = client.PostAsync(url, content).Result)
{
response.EnsureSuccessStatusCode();
return response.Content.ReadAsAsync<MyClass>().Result;
}
}

Accidentally sticking SOAP Body inside of SOAP Header attempting to access WSE service with WCF client

I am attempting to hit a WSE secured web service using WCF. The SOAP header I need to generate to do this should contain a username, password, nonce, and created date... Here is a an example of a soap UI header that I use to hit the same service...
<soap:Header>
<wsse:Security soap:mustUnderstand="true" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-2" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>----------</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">----------</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">Hozef94FFwOhuiF5QixaMQ==</wsse:Nonce>
<wsu:Created>2012-08-21T13:26:03.642Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
Now I found a tutorial that was pretty usefull. usefull tutorial
I have successfully implemented it... But I am now sticking the SOAP body into the soap header and there isn't a nonce generated.
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo9VZylDHg5JMgjsNnWLhATkAAAAA+YtOxHdh0Uqd4a64raX/nIzYz20mPHlBv4Wk5S8d5PsACQAA</VsDebuggerCausalityData>
<wsse:Security s:mustUnderstand="0" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<UsernameToken xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<Username>------------</Username>
<Password>************</Password>
</UsernameToken>
</wsse:Security>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetOrganizations xmlns="http://------------------------/businessobjects/messaging/">
<personId xmlns="">0</personId>
<typeId xmlns="">
<int>1</int>
<int>2</int>
<int>3</int>
<int>4</int>
</typeId>
</GetOrganizations>
</s:Body>
</s:Header>
</s:Envelope>
Don't know why it is doing this. I followed the tutorial to the letter. My Repository getting everything...
using (DistListServiceReference.DistributionListClient dlc = new DistListServiceReference.DistributionListClient())
{
try
{
PasswordDigestBehavior behavior = new PasswordDigestBehavior("********", "********");
dlc.Endpoint.Behaviors.Add(behavior);
GetDistributionLists gdl = new GetDistributionLists();
gdl.PersonID = 0;
GetDistributionListsResponse gdlr = new GetDistributionListsResponse();
gdlr = dlc.GetDistributionLists(gdl);
return gdlr;
}
catch (Exception e)
{
dlc.Abort();
return null;
}
}
My PasswordDigentInspector
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
// Use the WSE 3.0 security token class
UsernameToken token = new UsernameToken(this.Username, this.Password, PasswordOption.SendPlainText);
WseHeader header = new WseHeader(this.Username, this.Password);
// Serialize the token to XML
XmlElement securityToken = token.GetXml(new XmlDocument());
MessageHeader securityHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken", securityToken, false);
request.Headers.Add(header);
// complete
return Convert.DBNull;
}
How I apply client behavior
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new PasswordDigestMessageInspector(this.Username, this.Password));
}
Everything is pretty much there. I am not seing where the body is being injected into the header. Any body have any ideas?
UPDATE: Debugging
I am looking at the the actual header that I inject into the soap message and this is what I see...
{<wsse:Security s:mustUnderstand="0" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<UsernameToken xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<Username>**********</Username>
<Password>************</Password>
</UsernameToken>} System.ServiceModel.Channels.MessageHeaderInfo {TestDistListApplication.Repository.WseHeader}
Just looking at that, there is no nonce, I could build it programmatically, but I am not sure if that is a good idea. Especially since there is a nonce already and a create date in the securityToken... Not sure why they don't appear in the header though...
<wsse:Username xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">MedTrak_Dev</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">uncJUN132012</wsse:Password>
<wsse:Nonce xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">mvy9nUfF+rnT3oTasDBqxg==</wsse:Nonce>
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2012-08-28T13:30:42Z</wsu:Created>
I have the debugger stopped and I am watching both those variables. What might cause the header not to have those things and the the WSEHeader to have them?
Looks like I will have to debugg that.
Without running your code myself it is a little tricky to understand why my example doesn't work in your scenario. However, in a attempt to debug this I would try the following:
Stick a breakpoint on this line:
XmlElement securityToken = token.GetXml(new XmlDocument());
And after stepping through this line, see what the securityToken XML looks like. It is this XML which is being injected into the security header. It isn't any more complicated than that. You coudl create this XML manually if you want. I used the WSE 3.0 classes because I didn't want to hand code the XML myself.
I also notice that your security token definition looks slightly different to mine. My example was:
MessageHeader securityHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", securityToken, false);
Yours:
MessageHeader securityHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken", securityToken, false);
The namespaces are different. Any reason for this?
If the breakpoint added from the step above isn't hit then it would suggest that the behavior isn't actually being applied. In which case, you'll probably need to double check your configs to make sure that the behavior is correctly applied on the client. Alternatively (as my example shows) you can programatically add the custom behavior before sending the message.
I recommend to anyone dealing with WCF issues, that they first turn on verbose logging and also use fiddler to see what is actually going over the wire.

WCF Client - How to process or ignore a MustUnderstand header element?

I'm writing a WCF Client that consumes a non-.Net web service, using WS-Security. The service's response contains a Security header with mustUnderstand set to true.
Using a ServiceModelListener, I do see actual data coming back from the service. The WCF client fails, however, because it is not processing the Security header.
<env:Header>
<wsse:Security env:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Timestamp wsu:Id="timestamp">
<wsu:Created>2012-03-28T13:43:54.474Z</wsu:Created>
<wsu:Expires>2012-03-28T13:48:54.474Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</env:Header>
WCF Client Error Message:
The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood by the recipient of this message, causing the message to not be processed. This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process. Please ensure that the configuration of the client's binding is consistent with the service's binding.
My WCF client doesn't need any of the timestamp info. Is there an easy way to stub in a processing routine? I've already tried extending the Response class & adding a [MessageHeader] property.
EDIT:
Asked another way: How do I implement a WCF client that accepts custom header elements that are marked Must Understand?
I ran into a similar issue. I am not sure if this is useful or not.
MSDN WCF Extensibility
http://blogs.msdn.com/b/carlosfigueira/archive/2011/04/19/wcf-extensibility-message-inspectors.aspx
The setup here is Certificate based, Oracle Application Server 10g, and .Net to consume the services. Using SOAPUi was very useful while trying to figure out what was happening with the Request and then the response.
I have not tried modifying the code to use basicHttpBinding, but I used WSHttpBinding as the base of my configuration in code. Then used
WSHttpBinding binding = new WSHttpBinding()
{
CloseTimeout = new TimeSpan(0, 1, 0),
OpenTimeout = new TimeSpan(0, 1, 0),
SendTimeout = new TimeSpan(0, 1, 0),
AllowCookies = false,
BypassProxyOnLocal = false,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize = 524288,
MaxReceivedMessageSize = 65536,
MessageEncoding = WSMessageEncoding.Text,
UseDefaultWebProxy = false,
ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas()
{
MaxDepth = 32,
MaxArrayLength = 16384,
MaxBytesPerRead = 4096,
MaxNameTableCharCount = 16384,
MaxStringContentLength = 8192
}
};
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
binding.Security.Transport.Realm = string.Empty;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
binding.Security.Message.EstablishSecurityContext = true;
binding.Security.Message.NegotiateServiceCredential = true;
CustomBinding customBinding = new CustomBinding();
BindingElementCollection collection = binding.CreateBindingElements();
Looped through for the TextMessageEncodingBindingElement to set Soap11 and AddressingVersion to None.
foreach (BindingElement element in collection)
{
if (typeof(TextMessageEncodingBindingElement) == element.GetType())
{
TextMessageEncodingBindingElement item = element as TextMessageEncodingBindingElement;
if (null != item)
{
item.MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap11, AddressingVersion.None);
customBinding.Elements.Add(item);
}
}
else
customBinding.Elements.Add(element);
}
I used the ChannelFactory and added an EndPoint Behavior for a Message Inspector.
At this point I then had control of the request and I could add the appropriate header and modified the mustUnderstand on the Action.
Using SOAPUi I took my Message.ToString() and put that in SOAPUI and tested the request. Once the items that were needed were added to the request, it was then determined that the OAS server was not replying with all the necessary elements. Using the message inspector for the reply I modified the message to include the missing headers. I can't remember where I found the base code for the message inspector, but you would need to modify your code to utlize it properly.
For my example here are some snippets.
For the transform message in
public object BeforeSendRequest
I needed to modify the Header, so using a for loop I grabbed the XElement and added the OASIS header and added a To header.
XNamespace xmlns = "http://schemas.xmlsoap.org/soap/envelope/";
XElement securityHeader = new XElement(
xmlns + "Security",
new XAttribute(xmlns + "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"),
new XAttribute(xmlns + "xmlns", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"),
new XAttribute(xmlns + "mustUnderstand", "0"));
element.Add(securityHeader);
I also had to modify the Action Header
else if (localName.Equals("Action", StringComparison.InvariantCultureIgnoreCase))
{
foreach (XAttribute a in element.Attributes())
{
if (a.Name.LocalName == "mustUnderstand")
a.Value = "0";
}
}
My problem was that the Service didn't reply with an Action Header
So in the
public void AfterReceiveReply
I called my TransformReply returning type Message with something like the following. You may need to modify values for the string.Empty, but this is just an example.
...
Message reply = Message.CreateMessage(message.Version, null, reader);
reply.Headers.Add(MessageHeader.CreateHeader("Action", string.Empty, string.Empty, false));
reply.Properties.CopyProperties(message.Properties);
...
I would really suggest using a tool such as SOUPUI to beable to mess with the envelope and see the reply. If you do SSL, you'll need to create a cacert file and place it in the SSLSettings of the preferences.
There is different standards of WS-Security. Might be it make sense to change the binding at client side, since basicHttpBinding and wsHttpBindings are working with different security standards.
Ran into an issue working on some code around IP cameras supporting ONVIF. Cameras were sending back Nonce and Created in Security element and WCF didn't like it. Ended up using IClientMessageInspector to catch the response, and re-flag the header as mustUnderstand=false.
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
//Some cameras produce WS-Security headers as a repsonse which contain a nonce and created date/time WCF doesn't like this for some reason.
//The WS-Security element contains mustUnderstand="true". When WCF can't process the unrecoginzed elements it throw an exception.
// The code below searches for a WS-Security header. If one is found it copies the message body and all headers but the WS-Security header.
// A new WS-Security header is then created with mustUnderstand=false and added into the new message. The proxy clients
// will still receive the WS-Security header, just won't throw exceptions because of Nonce and Created elements in the header.
if (reply.Headers.Count > 0)
{
//Have a WS-Security header?
int secHeaderIndex = reply.Headers.FindHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
if (secHeaderIndex < 0) { return; }
//Our replacement message
System.ServiceModel.Channels.Message cleanedMessage = null;
//Copy the body
cleanedMessage = Message.CreateMessage(reply.Version, "", reply.GetReaderAtBodyContents());
//Create a new WS-Security header with mustUnmderstand=false
MessageHeader newSecHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", reply.Headers[0], false);
for (int x=0; x<reply.Headers.Count; x++)
{
if (x == secHeaderIndex)
{//Don't copy the old WS-Security header
continue;
}
//Not a WS-Security header, copy to the new message.
cleanedMessage.Headers.CopyHeaderFrom(reply, x);
}
cleanedMessage.Headers.Add(newSecHeader);
reply = cleanedMessage;
}
}

Programmatic TFS 2010 Event registration + error HTTP code 415: Cannot process the message because the content

i want to use tfs event registration for reading out the BuildQualityChanged and the WorkItemChanged event. It was working some month before, now i get the following error while getting the event xml:
HTTP code 415: Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'. ---> System.Net.WebException: The remote server returned an error: (415) Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..
Im building a console application which will later be an windows service. The code for for hosting the wcf service is the following:
private void HostWcfService()
{
D_("Hosting WCF service");
var serviceUri = new Uri(GetCorrectServiceAddress());
_host = new ServiceHost(typeof(BuildQualityChanged), new []{serviceUri});
AddBehaviors(_host);
SetBinding(_host, serviceUri);
_host.Open();
}
private static void AddBehaviors(ServiceHost service)
{
var smb = service.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior();
service.Description.Behaviors.Add(smb);
}
smb.HttpGetEnabled = true;
var sdb = service.Description.Behaviors.Find<ServiceDebugBehavior>();
if (sdb == null)
{
sdb = new ServiceDebugBehavior();
service.Description.Behaviors.Add(sdb);
}
sdb.IncludeExceptionDetailInFaults = true;
}
private void SetBinding(ServiceHost _host, Uri serviceUri)
{
// create and configure an MTOM encoder
var mtom =
new TextMessageEncodingBindingElement(
MessageVersion.Soap11, Encoding.UTF8);
// create a CustomBinding using MTOM encoder
CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(
new HttpTransportBindingElement());
_host.AddServiceEndpoint(typeof(TFS.Build.ITeamFoundationEventSubscriber),
binding, serviceUri);
}
All solutions i found until now with this error does configuring the service in web.config, this is no possible solution for me, i have to host programmatically. The second solution i found is to use BasicHttpBinding rather than CustomBinding, but this does not work too for me, the tfs breaks with the same error. I was wondering that the SOAP Version in the WSDL by using BasicHttpBinding is SOAP 1.2 too. SetBinding() for using BasicHttpBinding is
var binding1 = new BasicHttpBinding();
binding1.Name = "binding1";
binding1.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
binding1.Security.Mode = BasicHttpSecurityMode.None;
_host.AddServiceEndpoint(typeof(TFS.Build.ITeamFoundationEventSubscriber), binding1, serviceUri);
The only difference in required and given request is the content type, i need application/soap+xml rather than application/xml
Can anyone tell me where my brain is going wrong?
Michael
After hours of searching i did not found any solution. A geek of microsoft helps me to get it rid:
TFS2010 only supports wsHttpBinding without security
The content of my SetBinding() method is now the following:
var binding1 = new WSHttpBinding();
binding1.Name = "binding1";
binding1.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
binding1.Security.Mode = SecurityMode.None;
_host.AddServiceEndpoint(typeof(TFS.Build.ITeamFoundationEventSubscriber), binding1, GetCorrectServiceAddress());
MAGIC, IT WORKS :)

How do I send SOAP Request to WCF Service?

Can anyone point me to an example how to post a SOAP Request to a WCF Service and return a SOAP Response? Basically a Travel client sends a SOAP request with search parameters and the WCF Service checks within the database and then sends the appropriate holidays.
I keep getting this error, with the method I have used: "The remote server returned an error: (400) Bad Request"
The error you got is because the server does not understand the HTTP request.
It could be the binding you configured or the service proxy is incorrect at client level.
Or the service you defined expects HTTP GET rather than HTTP POST. Sometimes the add service reference may not generate correct HTTP verb for some [WebGet] attributed operations. You may need to add [WebGet] for the operation at client side manually.
Either have a look at SoapUI, or locate the WcfTestClient buried deep in your Visual Studio folders (C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE).
Both can connect to a WCF service and send/receive SOAP messages.
Or create your own little client, using svcutil.exe:
svcutil.exe (service URL)
will create a little *.cs file and a *.config file for you, which you can then use to call the service.
Marc
You haven't given many details as to how far along you are with the service, so it's hard to say.
If this is literally the first hit to the service, this error could occur if WCF has not been registered properly with IIS. Specifically the .svc extension needs to be mapped to the ASP.NET ISAPI module.
thanks for taking the time out to answer this.
The service works fine, if a client creates a reference to my WCF Service and makes a method call, the appropriate response is sent.
I forgot to add, that my client is sends a HTTP Post Request to my WCF Service.
The appropriate response is then created and returned to the Client.
I can read the HTTP Request, however when i try and access the HTTP response, i get error -"The remote server returned an error: (400) Bad Request"
The error happens when the code reaches this line:
// Get the response.
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
See code below:
private void CreateMessage()
{
// Create a request using a URL that can receive a post.
WebRequest request = WebRequest.Create("http://www.XXXX.com/Feeds");
string postData = "<airport>Heathrow</airport>";
// user function
request.Method = "POST";
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/soap+xml; charset=utf-8";
request.ContentLength = byteArray.Length;
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
// Get the response.
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
// Display the status.
HttpContext.Current.Response.Write(((HttpWebResponse)response).StatusDescription);
// Get the stream containing content returned by the server.
dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Display the content.
HttpContext.Current.Response.Write(responseFromServer);
// Clean up the streams.
reader.Close();
dataStream.Close();
response.Close();
}
regards
Kojo
Note
The recommended way of accessing WCF Service from other .NET application is by using the "Connected Services" reference. Below I describe how you can create and send SOAP requests in a more manual (and not recommended for production code) manner.
In short
You need:
Content-Type: text/xml; charset=utf-8 header
SOAPAction: http://tempuri.org/YourServiceClass/YourAction header
Request content wrapped in SOAP envelope.
Longer version (example)
Lets take a WCF Service Application scaffolding as an example.
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}
Using Wireshark, I found out that the requests made the default way (connected service reference) contain Content-Type: text/xml; charset=utf-8 and SOAPAction: http://tempuri.org/IService1/GetData headers and following SOAP envelope:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetData xmlns="http://tempuri.org/"> <!-- Action name -->
<value>123</value> <!-- Parameters -->
</GetData>
</s:Body>
</s:Envelope>
Using Insomnia, I tested that it's all we need in order to make the request pass successfully, so now just need to port it to the C#:
// netcoreapp3.1
static async Task<string> SendHttpRequest(string serviceUrl, int value)
{
// Example params:
// serviceUrl: "http://localhost:53045/Service1.svc"
// value: 123
using var client = new HttpClient();
var message = new HttpRequestMessage(HttpMethod.Post, serviceUrl);
message.Headers.Add("SOAPAction", "http://tempuri.org/IService1/GetData"); // url might need to be wrapped in ""
var requestContent = #$"
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<GetData xmlns=""http://tempuri.org/"">
<value>{value}</value>
</GetData>
</s:Body>
</s:Envelope>
";
message.Content = new StringContent(requestContent, System.Text.Encoding.UTF8, "text/xml");
var response = await client.SendAsync(message);
if (!response.IsSuccessStatusCode)
throw new Exception("Request failed.");
var responseContent = await response.Content.ReadAsStringAsync();
/*
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetDataResponse xmlns="http://tempuri.org/">
<GetDataResult>You entered: {value}</GetDataResult>
</GetDataResponse>
</s:Body>
</s:Envelope>
*/
// Just a really ugly regex
var regex = new Regex(#"(<GetDataResult>)(.*)(<\/GetDataResult>)");
var responseValue = regex.Match(responseContent).Groups[2].Value;
return responseValue;
}
You can ofc. use WebClient instead of HttpClient if preferred.