I'm building a WCF router and my client uses Reliable Sessions. In this scenario when the client opens a channel a message is sent (establishing a Reliable Session?). Its contents is as follows:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-rx/wsrm/200702/CreateSequence</a:Action>
<a:MessageID>urn:uuid:1758f794-c5d3-4573-b252-7a07344cc257</a:MessageID>
<a:To s:mustUnderstand="1">http://localhost:8010/RouterService</a:To>
</s:Header>
<s:Body>
<CreateSequence xmlns="http://docs.oasis-open.org/ws-rx/wsrm/200702">
<AcksTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</AcksTo>
<Offer>
<Identifier>urn:uuid:64a12658-71d9-4967-88ec-9bb0610f7ecb</Identifier>
<Endpoint>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</Endpoint>
<IncompleteSequenceBehavior>DiscardFollowingFirstGap</IncompleteSequenceBehavior>
</Offer>
</CreateSequence>
</s:Body>
</s:Envelope>
The problem here is that the headers do not contain any information I can use to look up what service to route the message to. In Busatmante's router sample code she gets around this by adding a header to the endpoint:
<client>
<endpoint address="http://localhost:8010/RouterService" binding="ws2007HttpBinding"
bindingConfiguration="wsHttp"
contract="localhost.IMessageManagerService" >
<headers>
<Route xmlns="http://www.thatindigogirl.com/samples/2008/01" >http://www.thatindigogirl.com/samples/2008/01/IMessageManagerService</Route>
</headers>
</endpoint>
</client>
When the reliable session is opened the message contains this custom header.
<Route a:IsReferenceParameter="true" xmlns="http://www.thatindigogirl.com/samples/2008/01">http://www.thatindigogirl.com/samples/2008/01/IMessageManagerService</Route>
This is great; however, I have a requirement to configure the client programatically. I figured that the ChannelFactory Endpoint would have a Header object to which I could manually add my custom header. Unfortunately it does not. So I did some searching and found some recomendations to extend WCF by implementing an IClientMessageInspector to add my header and adding it as a behavior to my endpoint.
public class ContractNameMessageInspector : IClientMessageInspector {
private const string HEADER_NAME = "ContractName";
private readonly string _ContractName;
public ContractNameMessageInspector(string contractName) {
_ContractName = contractName;
}
#region IClientMessageInspector Members
public void AfterReceiveReply(ref Message reply, object correlationState) { }
public object BeforeSendRequest(ref Message request, IClientChannel channel) {
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject)) {
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (httpRequestMessage != null && string.IsNullOrEmpty(httpRequestMessage.Headers[HEADER_NAME])) {
httpRequestMessage.Headers[HEADER_NAME] = this._ContractName;
}
}
else {
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(HEADER_NAME, this._ContractName);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
#endregion
}
So when my client makes a service call the message contains the custom header but the message establishing the Reliable Sessions still does not.
So my question is; how do I add a custom header to the Endpoint programatically in such a way that the reliable session message contains it?
Many Thanks
Figured it out. Although there is no property or method to add a header to an EndpointAddress there is an optional parameter on the constructor.
_Binding = bindingFactory.GetBinding(serviceUri, typeof(T));
AddressHeader header = AddressHeader.CreateAddressHeader("ContractName", "NameSpace", typeof (T).ToString());
_Address = new EndpointAddress(serviceUri, header);
_ChannelFactory = new ChannelFactory<T>(_Binding, _Address);
Now when I receive the message establishing the reliable session it actually does contain my custom header. This kinda makes sense as the message inspector most likely only operates on dispatched messages while the message establishing the reliable session is generated by lower level WCF code.
This works for me
public TResult Invoke<TResult>(Func<TClient, TResult> func,MessageHeader header)
{
TClient client = default(TClient);
var sw = new Stopwatch();
try
{
sw.Start();
using (client = _channelFactory.CreateChannel())
{
using (OperationContextScope contextScope = new OperationContextScope(client))
{
OperationContext.Current.OutgoingMessageHeaders.Add(header);
return func.Invoke(client);
}
}
}
finally
{
CloseConnection(client);
Instrument(this.GetType().Name, sw);
}
}
To programmatically add address headers see MSDN's Address Headers where one can programmatically add a header such as:
var cl = new MyWCFClientContext();
var eab = new EndpointAddressBuilder(cl.Endpoint.Address);
eab.Headers.Add( AddressHeader.CreateAddressHeader("ClientIdentification", // Header Key
string.Empty, // Namespace
"JabberwockyClient")); // Header Value
cl.Endpoint.Address = eab.ToEndpointAddress();
Related
I am attempting to develop a new WCF service that is hosted using the ServiceHost object. I am able to get the console application to start and I can see that it is binding to port 80 via netstat. Using WireShark I am also able to see that the client is able to connect to that port and send over the data. I had a problem early on with the amount of data that is being sent in the SOAP message from the client, but was able to resolve that issue by setting the max receive size on the binding. The HTTP 500 error that I am getting is:
The message with Action '' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).
The following is my WCF code and my service code.
public class MyWCFService
{
private ServiceHost _selfHost;
public void Start()
{
Uri baseAddress = new Uri(#"http://192.168.1.10");
this._selfHost = new ServiceHost(typeof(MyServiceImpl), baseAddress);
try {
WebHttpBinding binding = new WebHttpBinding();
binding.MaxBufferSize = 524288;
binding.MaxReceivedMessageSize = 524288;
this._selfHost.AddServiceEndpoint(typeof(IMyServiceContract), binding, "MyService");
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
this._selfHost.Description.Behaviors.Add(smb);
this._selfHost.Open();
}
catch ( CommunicationException ce ) {
this._selfHost.Abort();
}
}
public void Stop()
{
this._selfHost.Close();
}
}
The following is my service contract. It is fairly simple and only has a single Operation. It is expected that it will be called upon the receipt of a SOAP based message.
[ServiceContract(Namespace = "http://www.exampe.com")]
public interface IMyServiceContract
{
[OperationContract (Action="http://www.example.com/ReportData", ReplyAction="*")]
string ReportData( string strReport );
}
The following the my implementation of the service contract
class MyServiceImpl : IMyServiceContract
{
public string ReportData( string strReport )
{
return "ok";
}
}
Here is what I am getting from my client (the strReport was very long so I excluded it)
POST /MyService HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://www.example.com/ReportData"
Host: 192.168.1.10
Content-Length: 233615
Expect: 100-continue
Connection: Keep-Alive
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<ReportData xmlns="http://www.example.com/">
<strReport>
........
</strReport>
</ReportData>
</soap:Body>
</soap:Envelope>
Any help resolving this issue would be greatly appreciated.
Regards,
Richard
Do you want your service to be a SOAP service or a REST service?
On the service side, it is configured to be a REST services (because you are using WebHttpBinding). However, the request from your client is a SOAP request. If your client is a WCF client, it is probably using wsHttpBinding or basicHttpBinding. Both of these are for SOAP.
You can either:
Change your service to use basicHttpBinding or wsHttpBindding to match your client (if you want to use SOAP), or
Change your client to use webHttpBinding (if you want to use REST). This will need more changes because your operation contract is not properly Attributed for REST. And anyway, WCF is not a good option for REST. The ASP.Net Web API is much simpler and better supported.
Also
the Action specified in your operation contract should be just ReportData rather than the namespace qualified version. You may not need it at all in fact.
you can remove the ReplyAction (or specify a proper value if your client needs it)
Generally, you do not need to specify these. I'm not an expert on the innards of SOAP but I believe that WCF will specify these values based on the method name if you don't specify them. Uniqueness of method names in .Net will ensure that the action/replyaction are unique in that case.
With these changes in place It Works On My Machine (TM)
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;
}
}
I get the following error when i try to consume my wcf service
Could not find endpoint element with name 'http://localhost:8080/Provider/basic' and contract 'Provider.IProvider' in the ServiceModel client configuration section
I can however connect to my base address through the WCF Test Client on http://localhost:8080/Provider
I've tried updating my service reference and that didn't work, Anyone know what is wrong with my setup
public ServiceHost ProviderServiceHost { get; set; }
private void StartProvider()
{
if (ProviderServiceHost != null)
Abort();
ProviderServiceHost = new ServiceHost(typeof(Provider), new Uri("http://localhost:8080/Provider"));
var binding = new BasicHttpBinding
{
Name = "basicBinding",
HostNameComparisonMode = HostNameComparisonMode.WeakWildcard,
Security = { Mode = BasicHttpSecurityMode.None }
};
var metadataBehavior = ProviderServiceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (metadataBehavior == null)
{
metadataBehavior = new ServiceMetadataBehavior { HttpGetEnabled = true };
ProviderServiceHost.Description.Behaviors.Add(metadataBehavior);
}
ProviderServiceHost.AddServiceEndpoint(typeof(IProvider), binding, "http://localhost:8080/Provider/basic");
ProviderServiceHost.Open();
}
My client is connecting like this
private static ProviderClient _proxy = new ProviderClient(http://localhost:8080/Provider/basic);
If i don't put in an address then i get this exception
Message "Could not find default endpoint element that references contract 'Provider.IProvider' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element." string
Are you using client side configuration? As it appears you're using the ClientBase proxy I expect you've used 'Add Service Reference...' and are just the default configuration file.
The overload that you're using is new ServiceClient(string endpointConfigurationName) - the string value represents a Name, not an Address. If you check your configuration file you should see a client section has been added:
<client>
<endpoint address="http://localhost:8080/Provider/basic"
binding="basicHttpBinding"
bindingConfiguration="basicBinding_IProvider"
contract="ServiceReference1.ITest"
name="basicBinding_IProvider" />
</client>
So if you change your Client constructor to use this Name property it should work better for you.
private static ProviderClient _proxy =
new ProviderClient("basicBinding_IProvider");
On the other hand if you are not using a configuration file and want to specify the address in code, you can use a different ClientBase constructor:
using System.ServiceModel;
// ...
_client = new ProviderClient(new BasicHttpBinding(),
new EndpointAddress("http://localhost:8080/Provider/basic"));
There are a number of different overloads in the ClientBase class (your ProviderClient) which allow you to specify various properties.
There is a self hosted WCF REST service, need to send an xml post message to it.
Seems like this question was asked and answered several times, but after trying every solution I still didn`t get any success.
Server: interface
[ServiceContract]
public interface ISDMobileService
{
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle=WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Xml, ResponseFormat=WebMessageFormat.Xml)]
int ProcessMessage(string inputXml);
}
Server: class
public class Service : ISDMobileService
{
public int ProcessMessage(string inputXml)
{
Console.WriteLine( "ProcessMessage : " + inputXml );
return 0;
}
}
Server: hosting
class Program
{
static void Main(string[] args)
{
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri("http://172.16.3.4:7310"));
WebHttpBinding webbind = new WebHttpBinding(WebHttpSecurityMode.None);
ServiceEndpoint ep = host.AddServiceEndpoint(typeof(ISDMobileService), webbind, "");
ServiceDebugBehavior stp = host.Description.Behaviors.Find<ServiceDebugBehavior>();
stp.HttpsHelpPageEnabled = false;
host.Open();
Console.WriteLine("Service is up and running. Press 'Enter' to quit >>>");
Console.ReadLine();
host.Close();
}
}
Request from fiddler without anything in the "Request Body" works just fine and fires break point inside ProcessMessage method of Service class, any variant of data in "Request Body",
e.g.: test || <inputXml>test</inputXml> || inputXml="test" || <?xml version="1.0" encoding="UTF-8" ?><inputXml>test</inputXml> etc. gives HTTP/1.1 400 Bad Request
Will appreciate any help with this
A few things:
Since you're using WebServiceHost, you don't need to explicitly add the service endpoint (call to host.AddServiceEndpoint(...) in your Main.
The operation takes a string parameter; if you want to send it in XML, you need to wrap the string in the appropriate element. Try this body and it should work:
Body:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">This is a string encoded in XML</string>
You can also send it in different formats, such as JSON. This request should work as well
POST http://.../ProcessMessage
Host: ...
Content-Type: application/json
Content-Length: <the actual length>
"This is a string encoded in JSON"
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.