When I attempt to throw a FaultException<T>, if I generate the Message manually, I get correct SOAP message. However, when I throw the FaultException<T> from my method on the server, I do not get a correct SOAP message. The SOAP message has a Header / Action value of http://www.w3.org/2005/08/addressing/soap/fault, and my Body / Fault / Detail element is missing completely.
Please have a look at the important sections of my code.
The Service Contract
[ServiceContract(Namespace = "http://namespace.com/fault")]
public interface IFaultService
{
[OperationContract(IsOneWay = false)]
[FaultContract(typeof (MyFault))]
void ThrowFaultException();
}
The implementation
(The message_fault and message are manually created for testing only)
public class FaultService : IFaultService
{
public void ThrowFaultException()
{
var fault = new MyFault("This is from the service");
var fault_exception = new FaultException<MyFault>
(fault,
"Fault Service Reason",
FaultCode.CreateReceiverFaultCode("FaultCodeName","http://namespace.com/fault"),
"http://namespace.com/fault/IFaultService/ThrowFaultExceptionMyFaultFault");
var message_fault = fault_exception.CreateMessageFault();
var message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, message_fault,
fault_exception.Action);
throw fault_exception;
}
}
The Data Contract
[DataContract(Namespace = "http://namespace.com/fault")]
public class MyFault
{
public MyFault(string Message)
{
this.Message = Message;
}
[DataMember]
public string Message { get; set; }
}
The web.config system.serviceModel section
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingNoSecurity">
<security mode="None"/>
</binding>
</wsHttpBinding>
<services>
<service name="FaultService">
<endpoint address="ws"
binding="wsHttpBinding"
bindingConfiguration="wsHttpBindingNoSecurity"
bindingNamespace="http://namespace.com/fault"
contract="IFaultService"
name="ws">
</endpoint>
</service>
</services>
...
</system.serviceModel>
Manually generated SOAP message
{<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://namespace.com/fault/IFaultService/ThrowFaultExceptionMyFaultFault</a:Action>
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Receiver</s:Value>
<s:Subcode>
<s:Value xmlns:a="http://namespace.com/fault">a:FaultCodeName</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-US">Fault Service Reason</s:Text>
</s:Reason>
<s:Detail>
<MyFault xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://namespace.com/fault">
<Message>This is from the service</Message>
</MyFault>
</s:Detail>
</s:Fault>
</s:Body>
</s:Envelope>}
But the actual SOAP that comes across is
<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://www.w3.org/2005/08/addressing/soap/fault</a:Action>
<a:RelatesTo>urn:uuid:a3587103-953a-43f6-9691-951c21de8418</a:RelatesTo>
<ActivityId CorrelationId="23bf0a82-f4ac-4227-85ab-600f6ffe6a6f" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">a0945fa9-f5a3-4958-8682-097a8315f0dc</ActivityId>
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-US">Fault Service Reason</s:Text>
</s:Reason>
</s:Fault>
</s:Body>
</s:Envelope>
Thanks in advance.
Well, chalk this one up to experience...
There was an implementation of IErrorHandler that was being added to the service in code, during the build up of the services for Dependency Injection. That implementation of the ProvideFault method converted all exceptions and faults to the non-generic fault that was returned to the client.
Once that implementation was corrected and the service references refreshed, everything worked as expected.
Related
I have added a service reference to the third party webservice and on making a WCF call from my console application, I get the error message below:
System.ServiceModel.ProtocolException: 'The content type application/xml; charset=utf-8 of the response message does not match the content type of the binding (text/xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly. The first 610 bytes of the response were: '<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:CancelServiceResponse">
<soapenv:Body>
<tns:CancelServiceResponse>
<CancelServiceResult>
<Status_Code>FAILED</Status_Code>
<Status_Description>Service_ID= not found.</Status_Description>
<Order_ID></Order_ID>
</CancelServiceResult>
</tns:CancelServiceResponse>
</soapenv:Body>
</soapenv:Envelope>
Config file as below:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IB2BService">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://thirdpartyendpointaddress"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IB2BService"
contract="b2bService.IB2BService" name="BasicHttpBinding_IB2BService" />
</client>
</system.serviceModel>
Can someone advise, what needs to be done to fix this issue? I have searched all over SO and have not been able to find how to overcome this.
There is no problem the code snippets seem to me. One thing must be noted is to make sure the binding is consistent between the server and the client and has the correct service endpoint. On the client end, we could generate the configuration with Adding service reference tool. What I consider the best reply is to give you an example of invocation service with TransportWithMessageCredential.
Server end (10.157.13.69. Console application)
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("https://localhost:11011");
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpsGetEnabled = true
};
sh.Description.Behaviors.Add(smb);
}
sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustUserNamePasswordVal();
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpsBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Opened += delegate
{
Console.WriteLine("Service is ready");
};
sh.Closed += delegate
{
Console.WriteLine("Service is clsoed");
};
sh.Open();
Console.ReadLine();
sh.Close();
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string SayHello();
}
public class MyService : IService
{
public string SayHello()
{
return $"Hello Stranger,{DateTime.Now.ToLongTimeString()}";
}
}
internal class CustUserNamePasswordVal : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "jack" || password != "123456")
{
throw new Exception("Username/Password is not correct");
}
}
}
Bind the certificate to the port.
netsh http add sslcert ipport=0.0.0.0:11011 certhash=6e48c590717cb2c61da97346d5901b260e983850 appid={ED4CE60F-6B2E-4EE6-828F-C1A6A1B12565}
Client end (calling service by adding service reference)
var client = new ServiceReference1.ServiceClient();
client.ClientCredentials.UserName.UserName = "jack";
client.ClientCredentials.UserName.Password = "123456";
try
{
var result = client.SayHello();
Console.WriteLine(result);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Configuration file(auto-generated)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService">
<security mode="TransportWithMessageCredential" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://10.157.13.69:11011/" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService" contract="ServiceReference1.IService"
name="BasicHttpBinding_IService" />
</client>
</system.serviceModel>
Feel free to let me know if there is anything I can help with.
in my application I am referencing a WSDL service by "Add Service Reference"
then a service reference is created. with this entry in web.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IExternalOrder" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://xx.xxx.com/ExternalOrder.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IExternalOrder"
contract="WebReference.IExternalOrder" name="BasicHttpBinding_IExternalOrder" />
</client>
</system.serviceModel>
at this point I am trying to call this WSDL functions, in order to get the data that I need:
var GetOrderDetails_answer = ServiceHelper.GetOrderDetails(creds, this.OrderId);
inside ServiceHelper class:
public static GetAdminOrderDetailResponse GetOrderDetails(Request creds, string orderId)
{
////////////////////////////////////////////////////
ExternalOrderClient EOC = new ExternalOrderClient();
////////////////////////////////////////////////////
var GAOD = new GetAdminOrderDetailRequest
{
OrderID = orderId,
Password = creds.Password,
SiteID = creds.SiteID,
UserName = creds.UserName
};
try
{
return EOC.GetAdminOrderDetail(GAOD);
}
catch (Exception e)
{
/// TODO: log the error
return null;
}
}
The creation of the object ExternalOrderClient between "/////////////" lines
is throwing an exception:
Could not find default endpoint element that references contract
'WebReference.IExternalOrder' in the ServiceModel client configuration
section.
I tried every solution I could find (most of them are about calling the WSDL from other project, but i have only 1 project)
What is the fix for that?
I have a legacy Tibco SOAP service that I need to get some data from. Unfortunately this service is very particular about the XML namespace attributes on the request message. I have also run into this sort of problem when consuming services from PeopleSoft (https://en.wikipedia.org/wiki/PeopleCode).
I got the .wsdl from the service and created a service reference.
Out of the box, the XML request message that .Net produces is:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<getLocation xmlns="http://customNamespaceHere">
<context>
<source>SysAdmin</source>
</context>
<Address>
<address1>123 Main St</address1>
<city>New York</city>
<state>NY</state>
<country>US</country>
</Address>
</getLocation>
</s:Body>
</s:Envelope>
What actually works is (I figured this out using SoapUI):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<getLocation xmlns="http://customNamespaceHere">
<context>
<source>SysAdmin</source>
</context>
<Address>
<address1>123 Main St</address1>
<city>New York</city>
<state>NY</state>
<country>US</country>
</Address>
</getLocation>
</s:Body>
</s:Envelope>
Notice the absence of the xsi and xsd prefixes within the body tag.
Q: How can I get .Net to send the correct XML short of hand rolling the XML document and manually sending it to the service?
A: I was able to modify the XML request before sending it out by implementing IClientMessageInspector.
First I had to extend WCF by implementing IClientMessageInspector. This allowed me to gain access to the request object and modify the XML request message. These classes don't need to be in any particular location within you solution (as far as I know).
public class ExtendedClientMessageInspector : IClientMessageInspector
{
// Here we can alter the xml request body before it gets sent out.
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return TibcoService.ModifyGetLocationRequest(ref request);
} // end
public void AfterReceiveReply(ref Message reply, object correlationState)
{
return;
} //end
} // end class
public class ExtendedEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
} // end
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new ExtendedClientMessageInspector());
} // end
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
return;
} // end
public void Validate(ServiceEndpoint endpoint)
{
return;
} // end
} // end class
public class EndpointBehaviorExtensionElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ExtendedEndpointBehavior();
} // end
public override Type BehaviorType
{
get
{
return typeof(ExtendedEndpointBehavior);
}
} // end
} // end class
I did put the method that specifically deals with the XML in this service in the same class as the service:
public static Message ModifyGetLocationRequest(ref Message request)
{
// Initialize objects
var xmlDocument = new XmlDocument();
var memoryStream = new MemoryStream();
var xmlWriter = XmlWriter.Create(memoryStream);
var xmlAttribute = xmlDocument.CreateAttribute("xmlns", "api", "http://www.w3.org/2000/xmlns/");
xmlAttribute.Value = "http://customNamespaceHere";
// Write the xml request message into the memory stream
request.WriteMessage(xmlWriter);
// Clear the xmlWriter
xmlWriter.Flush();
// Place the pointer in the memoryStream to the beginning
memoryStream.Position = 0;
// Load the memory stream into the xmlDocument
xmlDocument.Load(memoryStream);
// Remove the attributes from the second node down form the top
xmlDocument.ChildNodes[1].ChildNodes[1].Attributes.RemoveAll();
// Reset the memoryStream object - essentially nulls it out
memoryStream.SetLength(0);
// ReInitialize the xmlWriter
xmlWriter = XmlWriter.Create(memoryStream);
// Write the modified xml request message (xmlDocument) to the memoryStream in the xmlWriter
xmlDocument.WriteTo(xmlWriter);
// Clear the xmlWriter
xmlWriter.Flush();
// Place the pointer in the memoryStream to the beginning
memoryStream.Position = 0;
// Create a new xmlReader with the memoryStream that contains the xmlDocument
var xmlReader = XmlReader.Create(memoryStream);
// Create a new request message with the modified xmlDocument
request = Message.CreateMessage(xmlReader, int.MaxValue, request.Version);
return request;
} // end
To get this all to work when the service is called you will need to modify your web.config or app.config. On your endpoint declaration you will need to specify a behaviorConfiguration element like so:
<client>
<endpoint address="http://1.2.3.4:1234/InventoryBinding"
binding="basicHttpBinding" bindingConfiguration="HttpSoapBinding"
contract="TibcoSvc.InventoryPort" name="InventoryPort"
behaviorConfiguration="clientInspectorsAdded" />
</client>
You will also need to specify the extension and the behavior as well:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="ExtendedEndpointBehavior" type="Sample.Integrations.EndpointBehaviorExtensionElement, Sample.Integrations, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="clientInspectorsAdded">
<ExtendedEndpointBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Note here that the extension name needs to be exactly what is returned by:
var foo = typeof(<PutYourNamespaceHereNoAngleBrackets>.EndpointBehaviorExtensionElement).AssemblyQualifiedName;
Here are a few links that I found helpful:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/01440583-d406-4426-8667-63c6eda431fa/remove-xmlnsxsi-and-xmlnsxsd-from-soap-request-body-tag-aspnet?forum=wcf
https://social.msdn.microsoft.com/Forums/vstudio/en-US/51547537-fdae-4837-9bd1-30e445d378e9/removing-xmlnsxsihttpwwww3org2001xmlschemainstance-and?forum=wcf
http://weblogs.asp.net/paolopia/writing-a-wcf-message-inspector
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector(v=vs.100).aspx
I am creating a server in .NET and a client application for Android. I would like to implement an authentication method which sends username and password to server and a server sends back a session string.
I'm not familiar with WCF so I would really appreciate your help.
In java I've written the following method:
private void Login()
{
HttpClient httpClient = new DefaultHttpClient();
try
{
String url = "http://192.168.1.5:8000/Login?username=test&password=test";
HttpGet method = new HttpGet( new URI(url) );
HttpResponse response = httpClient.execute(method);
if ( response != null )
{
Log.i( "login", "received " + getResponse(response.getEntity()) );
}
else
{
Log.i( "login", "got a null response" );
}
} catch (IOException e) {
Log.e( "error", e.getMessage() );
} catch (URISyntaxException e) {
Log.e( "error", e.getMessage() );
}
}
private String getResponse( HttpEntity entity )
{
String response = "";
try
{
int length = ( int ) entity.getContentLength();
StringBuffer sb = new StringBuffer( length );
InputStreamReader isr = new InputStreamReader( entity.getContent(), "UTF-8" );
char buff[] = new char[length];
int cnt;
while ( ( cnt = isr.read( buff, 0, length - 1 ) ) > 0 )
{
sb.append( buff, 0, cnt );
}
response = sb.toString();
isr.close();
} catch ( IOException ioe ) {
ioe.printStackTrace();
}
return response;
}
But on the server side so far I haven't figured out anything.
I would be really thankful if anyone could explain how to create an appropriate method string Login(string username, string password) with appropriate App.config settings and Interface with appropriate [OperationContract] signature in order to read these two parameters from client and reply with session string.
Thanks!
To get started with WCF, it might be easiest to just use the default SOAP format and HTTP POST (rather than GET) for the web-service bindings. The easiest HTTP binding to get working is "basicHttpBinding". Here is an example of what the ServiceContract/OperationContract might look like for your login service:
[ServiceContract(Namespace="http://mycompany.com/LoginService")]
public interface ILoginService
{
[OperationContract]
string Login(string username, string password);
}
The implementation of the service could look like this:
public class LoginService : ILoginService
{
public string Login(string username, string password)
{
// Do something with username, password to get/create sessionId
// string sessionId = "12345678";
string sessionId = OperationContext.Current.SessionId;
return sessionId;
}
}
You can host this as a windows service using a ServiceHost, or you can host it in IIS like a normal ASP.NET web (service) application. There are a lot of tutorials out there for both of these.
The WCF service config might look like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="LoginServiceBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WcfTest.LoginService"
behaviorConfiguration="LoginServiceBehavior" >
<host>
<baseAddresses>
<add baseAddress="http://somesite.com:55555/LoginService/" />
</baseAddresses>
</host>
<endpoint name="LoginService"
address=""
binding="basicHttpBinding"
contract="WcfTest.ILoginService" />
<endpoint name="LoginServiceMex"
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
(The MEX stuff is optional for production, but is needed for testing with WcfTestClient.exe, and for exposing the service meta-data).
You'll have to modify your Java code to POST a SOAP message to the service. WCF can be a little picky when inter-operating with non-WCF clients, so you'll have to mess with the POST headers a little to get it to work. Once you get this running, you can then start to investigate security for the login (might need to use a different binding to get better security), or possibly using WCF REST to allow for logins with a GET rather than SOAP/POST.
Here is an example of what the HTTP POST should look like from the Java code. There is a tool called "Fiddler" that can be really useful for debugging web-services.
POST /LoginService HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://mycompany.com/LoginService/ILoginService/Login"
Host: somesite.com:55555
Content-Length: 216
Expect: 100-continue
Connection: Keep-Alive
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<Login xmlns="http://mycompany.com/LoginService">
<username>Blah</username>
<password>Blah2</password>
</Login>
</s:Body>
</s:Envelope>
Another option might be to avoid WCF all-together and just use a .NET HttpHandler. The HttpHandler can grab the query-string variables from your GET and just write back a response to the Java code.
You will need something more that a http request to interact with a WCF service UNLESS your WCF service has a REST interface. Either look for a SOAP web service API that runs on android or make your service RESTful. You will need .NET 3.5 SP1 to do WCF REST services:
http://msdn.microsoft.com/en-us/netframework/dd547388.aspx
From my recent experience i would recommend ksoap library to consume a Soap WCF Service, its actually really easy, this anddev thread migh help you out too.
If I were doing this I would probably use WCF REST on the server and a REST library on the Java/Android client.
I'm trying to log the requests and responses (the raw XML SOAP envelope) between a console application developed by me and a specific third party remote SOAP web service to database for audit purposes, and I can't find a way to do it.
Ideally what I'd like to do is getting the request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:SayHello>
<tem:name>Albireo</tem:name>
</tem:SayHello>
</soapenv:Body>
</soapenv:Envelope>
and the response
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<SayHelloResponse xmlns="http://tempuri.org/">
<SayHelloResult>Hello, Albireo.</SayHelloResult>
</SayHelloResponse>
</s:Body>
</s:Envelope>
and save them in the database.
So far every tutorial on the net I found boils down to two approaches, the SoapExtension method and the tracing method.
The SoapExtension method
The SoapExtension method is based on the SOAP Message Modification Using SOAP Extensions guide, in this method you create a class inheriting from SoapExtension and hook it in the application's configuration, the class' ProcessMessage method will allow you to intercept the SOAP messages.
This is an example of the class inherited from SoapExtension:
namespace Playground.Client
{
using System;
using System.Web.Services.Protocols;
public class SoapLogger : SoapExtension
{
public override object GetInitializer(System.Type serviceType)
{
throw new NotImplementedException();
}
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
throw new NotImplementedException();
}
public override void Initialize(object initializer)
{
throw new NotImplementedException();
}
public override void ProcessMessage(SoapMessage message)
{
throw new NotImplementedException();
}
}
}
And this is how it is wired in the configuration:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IGreeterService" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8080/greeter"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IGreeterService"
contract="Services.IGreeterService"
name="BasicHttpBinding_IGreeterService" />
</client>
</system.serviceModel>
<system.web>
<webServices>
<soapExtensionTypes>
<add group="0"
priority="1"
type="Playground.Client.SoapLogger" />
</soapExtensionTypes>
</webServices>
</system.web>
</configuration>
The problem with this method is it seems to work only for web applications, trying to implement it in a console application yield no result.
The tracing method
The tracing method is based upon the Configuring Message Logging guide, in this method you enable .NET's tracing for every SOAP/WCF communication in the application and dump the log somewhere (more information on the configuration can be found in Recommended Settings for Tracing and Message Logging).
This is an example of the tracing configuration:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.5" />
</startup>
<system.diagnostics>
<sources>
<source name="System.ServiceModel"
propagateActivity="true"
switchValue="Verbose, ActivityTracing">
<listeners>
<add initializeData="ServiceModel.svclog"
name="ServiceModel"
type="System.Diagnostics.XmlWriterTraceListener" />
</listeners>
</source>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add initializeData="MessageLogging.svclog"
name="MessageLogging"
type="System.Diagnostics.XmlWriterTraceListener" />
</listeners>
</source>
</sources>
</system.diagnostics>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IGreeterService" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8080/greeter"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IGreeterService"
contract="Services.IGreeterService"
name="BasicHttpBinding_IGreeterService" />
</client>
<diagnostics>
<endToEndTracing activityTracing="true"
messageFlowTracing="true"
propagateActivity="true" />
<messageLogging logEntireMessage="true"
logKnownPii="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true" />
</diagnostics>
</system.serviceModel>
</configuration>
The content of ServiceModel.svclog and MessageLogging.svclog can be found in a GitHub's Gist as it's too big to fit here.
The problem with this method is it logs every SOAP/WCF message in the application and it seems the generated logs are not really useful, they contains loads of informations and I can't understand if and how filter only what I'm interested in, the only practical way to read them seems to be Microsoft's Service Trace Viewer.
I've tried to add a custom TraceListener too:
namespace Playground.Client
{
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
public class CustomTraceListener : TraceListener
{
public override void Write(string message)
{
File.AppendAllLines("CustomTraceListener.txt", new[] { message });
}
public override void WriteLine(string message)
{
message = this.FormatXml(message);
File.AppendAllLines("CustomTraceListener.txt", new[] { message });
}
private string FormatXml(string message)
{
using (var stringWriter = new StringWriter())
{
var xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Encoding = Encoding.UTF8;
xmlWriterSettings.Indent = true;
xmlWriterSettings.OmitXmlDeclaration = true;
using (var xmlTextWriter = XmlWriter.Create(stringWriter, xmlWriterSettings))
{
XDocument.Parse(message).Save(xmlTextWriter);
}
return Convert.ToString(stringWriter);
}
}
}
}
But even though it allows me to intercept the messages, it doesn't save any metadata:
<MessageLogTraceRecord Time="2013-07-16T10:50:04.5396082+02:00" Source="ServiceLevelSendRequest" Type="System.ServiceModel.Channels.BodyWriterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<HttpRequest>
<Method>POST</Method>
<QueryString></QueryString>
<WebHeaders>
<VsDebuggerCausalityData>uIDPo4bOsuSXlSVEkmfof4AP2psAAAAAlEIoNto3KEWKgCnIGryjp9f3wbRlp+ROhY9Oy6bed/cACQAA</VsDebuggerCausalityData>
</WebHeaders>
</HttpRequest>
<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/IGreeterService/SayHello</Action>
<ActivityId CorrelationId="964a7c4f-3b18-4b5d-8085-e00ae03b58d1" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">80101cc1-dfb5-4c8e-8d19-ec848ab69100</ActivityId>
</s:Header>
<s:Body>
<SayHello xmlns="http://tempuri.org/">
<name>Albireo</name>
</SayHello>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
<MessageLogTraceRecord Time="2013-07-16T10:50:04.6176897+02:00" Source="TransportSend" Type="System.ServiceModel.Channels.BodyWriterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<Addressing>
<Action>http://tempuri.org/IGreeterService/SayHello</Action>
<To>http://localhost:8080/greeter</To>
</Addressing>
<HttpRequest>
<Method>POST</Method>
<QueryString></QueryString>
<WebHeaders>
<VsDebuggerCausalityData>uIDPo4bOsuSXlSVEkmfof4AP2psAAAAAlEIoNto3KEWKgCnIGryjp9f3wbRlp+ROhY9Oy6bed/cACQAA</VsDebuggerCausalityData>
</WebHeaders>
</HttpRequest>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<ActivityId CorrelationId="964a7c4f-3b18-4b5d-8085-e00ae03b58d1" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">80101cc1-dfb5-4c8e-8d19-ec848ab69100</ActivityId>
</s:Header>
<s:Body>
<SayHello xmlns="http://tempuri.org/">
<name>Albireo</name>
</SayHello>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
With this information it's impossible to rebuild the request/response flow as all the messages are mixed together.
Compare them to the *.svclog generated by the native XmlWriterTraceListener:
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
<System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
<EventID>0</EventID>
<Type>3</Type>
<SubType Name="Information">0</SubType>
<Level>8</Level>
<TimeCreated SystemTime="2013-07-16T08:50:04.6176897Z" />
<Source Name="System.ServiceModel.MessageLogging" />
<Correlation ActivityID="{80101cc1-dfb5-4c8e-8d19-ec848ab69100}" />
<Execution ProcessName="Playground.Client" ProcessID="4348" ThreadID="1" />
<Channel />
<Computer>ESP-DEV-9</Computer>
</System>
<ApplicationData>
<TraceData>
<DataItem>
<MessageLogTraceRecord Time="2013-07-16T10:50:04.6176897+02:00" Source="TransportSend" Type="System.ServiceModel.Channels.BodyWriterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<Addressing>
<Action>http://tempuri.org/IGreeterService/SayHello</Action>
<To>http://localhost:8080/greeter</To>
</Addressing>
<HttpRequest>
<Method>POST</Method>
<QueryString></QueryString>
<WebHeaders>
<VsDebuggerCausalityData>uIDPo4bOsuSXlSVEkmfof4AP2psAAAAAlEIoNto3KEWKgCnIGryjp9f3wbRlp+ROhY9Oy6bed/cACQAA</VsDebuggerCausalityData>
</WebHeaders>
</HttpRequest>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<ActivityId CorrelationId="964a7c4f-3b18-4b5d-8085-e00ae03b58d1" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">80101cc1-dfb5-4c8e-8d19-ec848ab69100</ActivityId>
</s:Header>
<s:Body>
<SayHello xmlns="http://tempuri.org/">
<name>Albireo</name>
</SayHello>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
</E2ETraceEvent>
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
<System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
<EventID>0</EventID>
<Type>3</Type>
<SubType Name="Information">0</SubType>
<Level>8</Level>
<TimeCreated SystemTime="2013-07-16T08:50:04.6957712Z" />
<Source Name="System.ServiceModel.MessageLogging" />
<Correlation ActivityID="{80101cc1-dfb5-4c8e-8d19-ec848ab69100}" />
<Execution ProcessName="Playground.Client" ProcessID="4348" ThreadID="1" />
<Channel />
<Computer>ESP-DEV-9</Computer>
</System>
<ApplicationData>
<TraceData>
<DataItem>
<MessageLogTraceRecord Time="2013-07-16T10:50:04.6801549+02:00" Source="TransportReceive" Type="System.ServiceModel.Channels.BufferedMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<HttpResponse>
<StatusCode>OK</StatusCode>
<StatusDescription>OK</StatusDescription>
<WebHeaders>
<Content-Length>207</Content-Length>
<Content-Type>text/xml; charset=utf-8</Content-Type>
<Date>Tue, 16 Jul 2013 08:50:04 GMT</Date>
<Server>Microsoft-HTTPAPI/2.0</Server>
</WebHeaders>
</HttpResponse>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header></s:Header>
<s:Body>
<SayHelloResponse xmlns="http://tempuri.org/">
<SayHelloResult>Hello, Albireo.</SayHelloResult>
</SayHelloResponse>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
</E2ETraceEvent>
Here the <Correlation ActivityID="{80101cc1-dfb5-4c8e-8d19-ec848ab69100}" /> tag establishes a relation between each request and response, allowing a developer to rebuild the whole session.
Is there a way to accomplish what I'm trying to do?
If the service is registered as a WCF web-service (not as an old-school ASMX web-service) it's possible to do it through IClientMessageInspector and IEndpointBehavior.
First you have to create a class inheriting from IClientMessageInspector that will handle the logging of both the requests and the replies.
namespace Playground.Sandbox
{
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
public class MyClientMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(
ref Message request,
IClientChannel channel)
{
// TODO: log the request.
// If you return something here, it will be available in the
// correlationState parameter when AfterReceiveReply is called.
return null;
}
public void AfterReceiveReply(
ref Message reply,
object correlationState)
{
// TODO: log the reply.
// If you returned something in BeforeSendRequest
// it will be available in the correlationState parameter.
}
}
}
Then you have to create a class inheriting from IEndpointBehavior that will register the inspector in the client.
namespace Playground.Sandbox
{
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
public class MyEndpointBehavior : IEndpointBehavior
{
public void Validate(
ServiceEndpoint endpoint)
{
}
public void AddBindingParameters(
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(
ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
}
public void ApplyClientBehavior(
ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
var myClientMessageInspector = new MyClientMessageInspector();
clientRuntime.ClientMessageInspectors.Add(myClientMessageInspector);
}
}
}
Then when you want to use the behavior you can manually register it before using the service.
namespace Playground.Sandbox
{
public static class Program
{
public static void Main()
{
using (var client = new MyWcfClient())
{
var myEndpointBehavior = new MyEndpointBehavior();
client.Endpoint.Behaviors.Add(myEndpointBehavior);
// TODO: your things with the client.
}
}
}
}
If you don't want to register the behavior manually or you need it to be always active, you can register it in the configuration file.
First you need to create a class inheriting from BehaviorExtensionElement, this class will tell the .NET Framework which behavior will be applied and will create the instance when needed.
namespace Playground.Sandbox
{
using System;
using System.ServiceModel.Configuration;
public class MyBehaviorExtensionElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
var myEndpointBehavior = new MyEndpointBehavior();
return myEndpointBehavior;
}
public override Type BehaviorType
{
get
{
return typeof(MyEndpointBehavior);
}
}
}
}
Then you need to register the BehaviorExtensionElement in the configuration file (only the relevant part of the configuration file are shown).
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime sku=".NETFramework,Version=v4.5"
version="v4.0" />
</startup>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="withMyBehaviorExtensionElement">
<myBehaviorExtensionElement />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="..."
behaviorConfiguration="withMyBehaviorExtensionElement"
binding="..."
bindingConfiguration="..."
contract="..."
name="..." />
</client>
<extensions>
<behaviorExtensions>
<add name="myBehaviorExtensionElement"
type="Playground.Sandbox.MyBehaviorExtensionElement, Playground.Sandbox" />
</behaviorExtensions>
</extensions>
</system.serviceModel>
</configuration>
Now you can use the service without manually registering the behavior each time:
namespace Playground.Sandbox
{
public static class Program
{
public static void Main()
{
using (var client = new MyWcfService())
{
// TODO: your things with the client.
}
}
}
}
You can find a guide on how to do this in the MSDN's Message Inspectors article.