WCF message logging - add filters with XPath queries - wcf

I have a WCF service with the following contract:
[ServiceContract(Namespace="http://myNamespace.org/")]
public interface IMyService
{
[OperationContract]
string Invert(string s);
[OperationContract]
string ToUpper(string s);
}
Clients call both methods, Invert and ToUpper. Imagine I want to use message logging, but the only method I'm interested in is ToUpper as the other method is heavily used and logging all the messages would blow the log ;)
Here, I read how to filter the messages that are written into the log. But I must be doing something wrong as my log remains empty... My config looks like this
<system.serviceModel>
...
<diagnostics>
<messageLogging logEntireMessage="true" logMessagesAtServiceLevel="false" logMalformedMessages="true" logMessagesAtTransportLevel="true">
<filters>
<add xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">/soap:Envelope/soap:Header/a:Action[starts-with(text(),'http://myNamespace.org/IMyService/ToUpper')]</add>
</filters>
</messageLogging>
</diagnostics>
</system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add name="ServiceModelTraceListener" />
</listeners>
</source>
</sources>
<sharedListeners>
<add initializeData="LogServer.svclog" type="System.Diagnostics.XmlWriterTraceListener" name="ServiceModelTraceListener" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>
If I apply this filter, there won't go a single message into the log...
So what am I doing wrong regarding the linked example above?
Without the filter the xml trace of a default message (method ToUpper invoked with string parameter hello) looks like this:
<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="2011-05-27T17:53:53.9908714Z" />
<Source Name="System.ServiceModel.MessageLogging" />
<Correlation ActivityID="{00000000-0000-0000-0000-000000000000}" />
<Execution ProcessName="WcfLoggingTest.Host.vshost" ProcessID="4324" ThreadID="12" />
<Channel />
<Computer>MY-Machine</Computer>
</System>
<ApplicationData>
<TraceData>
<DataItem>
<MessageLogTraceRecord Time="2011-05-27T19:53:53.9908714+02:00" Source="TransportReceive" Type="System.ServiceModel.Channels.BufferedMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<HttpRequest>
<Method>POST</Method>
<QueryString></QueryString>
<WebHeaders>
<VsDebuggerCausalityData>uIDPozEtlPQCjkhCodYdPWh6joUAAAAAamILDP7v3kG5sY6zKsB7HPPiLBWr+AVGmfFDQbk8GYAACQAA</VsDebuggerCausalityData>
<SOAPAction>"http://myNamespace.org/IMyService/ToUpper"</SOAPAction>
<Content-Length>157</Content-Length>
<Content-Type>text/xml; charset=utf-8</Content-Type>
<Accept-Encoding>gzip, deflate</Accept-Encoding>
<Expect>100-continue</Expect>
<Host>localhost:8731</Host>
</WebHeaders>
</HttpRequest>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://localhost:8731/Design_Time_Addresses/MyService/</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://myNamespace.org/IMyService/ToUpper</Action>
</s:Header>
<s:Body>
<ToUpper xmlns="http://myNamespace.org/">
<s>hello</s>
</ToUpper>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
</E2ETraceEvent>
Update:
For for every body who's interested in the solution, I finally got it working with jasso's help, thanks:
<add xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:a="http://schemas.microsoft.com/ws/2005/05/addressing/none">/soap:Envelope/soap:Header/a:Action[starts-with(text(),'http://myNamespace.org/IMyService/ToUpper')]</add>
I then edited my Interface and added the methods Method1 till Method3. My goal was then to log everything except the messages related to Method1 and Method3. I did this with the following filter:
<add xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:a="http://schemas.microsoft.com/ws/2005/05/addressing/none">/soap:Envelope/soap:Header/a:Action[starts-with(text(),'http://myNamespace.org/IMyService/Method1')=false() and starts-with(text(),'http://myNamespace.org/IMyService/Method3')=false()]</add>
This way, only the messages related to Invert, ToUpper and Method2 are logged.
It may be a cleaner approach to handle this with two seperate filters, but for the moment I'm quite happy with this.

You are using a wrong namespace for the Action element in your XPath expression
You have
xmlns:a="http://www.w3.org/2005/08/addressing"
... /a:Action[starts-with ...
and the document has
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">
So the namespaces differ, because Action element has a default namespace definition attached to it.
Also your XPath is searching for a soap:Envelope root element because your expression starts with a /. I'm not familiar with the framework, it might select a subtree from your example XML (the soap content) and then apply the XPath filter. If this is not the case and your XPath should produce a match on that given XML document, then you should start the expression with // or with a path to soap:Envelope element (like /*/*/*/*/*/soap:Envelope). Using // operator in the beginning is inefficient since it requires going through all the nodes in the whole document.

Very thanks for the useful information!
According to my own study, to perform the filtering successfully, these key points must be enforced as well:
do not start the XPath expression with double "/", else the filtering will not work at all. Although this double "/" is correct regarding to XPath syntax (the same as what jasso pointed at May 27 23:06, 2011).
do not rely on the WCF Configuration Edit tool to choose the alias for the involved xml namespace. Till now it is found that "s12" does not work, but "s" or "s1a" is OK (Maybe it is a bug done by Microsoft, I am not sure).

The XPath query is probably the problem. Try this simpler version:
<filters>
<add xmlns:msgtr="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace" >//msgtr:SOAPAction[contains(.,'ToUpper')]</add>
</filters>

Related

How can read config file in Net5?

I using Vb.net , FW: Net5
This is my app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ConnectString" value="DB/Sample.db3" />
</appSettings>
<system.diagnostics>
<sources>
<!-- This section defines the logging configuration for My.Application.Log -->
<source name="DefaultSource" switchName="DefaultSwitch">
<listeners>
<add name="FileLog"/>
<!-- Uncomment the below section to write to the Application Event Log -->
<!--<add name="EventLog"/>-->
</listeners>
</source>
</sources>
<switches>
<add name="DefaultSwitch" value="Information" />
</switches>
<sharedListeners>
<add name="FileLog"
type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"
initializeData="FileLogWriter"/>
<!-- Uncomment the below section and replace APPLICATION_NAME with the name of your application to write to the Application Event Log -->
<!--<add name="EventLog" type="System.Diagnostics.EventLogTraceListener" initializeData="APPLICATION_NAME"/> -->
</sharedListeners>
</system.diagnostics>
</configuration>
I install "System.Configuration.ConfigurationManager" from Nuget
I had try get config as:
Dim str = System.Configuration.ConfigurationManager.AppSettings("ConnectString")
But it occur exception error:
Configuration system failed to initialize
How can read app.config in VB Net5?
I tried using an app.config file in a .NET 5.0 app and I got the same exception. Unlike you, I looked a bit further and saw that it also said that it encountered an unknown section, i.e. system.diagnostics. I then commented out the <system.diagnostics> element in my config file and it worked as expected. I'm not sure what you would do if you actually needed to use that section but it doesn't look like you do so it shouldn't be a problem to comment it out or delete it.

WCF IClientMessageInspector Logging Discrepancy Regarding Action

I am logging outgoing WCF Soap requests from my application, and discovered a discrepancy between what I'm logging and what actually goes out to the server via the wire.
Here is what I'm logging:
<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/myinterface</Action>
</s:Header>
<s:Body>
<myinterface xmlns="http://tempuri.org/">
...
Here is what actually goes out (captured with WireShark):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<myinterface xmlns="http://tempuri.org/">
...
You'll notice that the WireShark capture does not have the Header or Action elements.
I only noticed this because I tried pushing my logged request out through SoapUI.
I'm using this code to log the request:
public class InspectorBehavior : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new WCFMessageInspector());
}
And:
public class WCFMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
{
Log(request.ToString());
return null;
}
And:
client.Endpoint.Address = new System.ServiceModel.EndpointAddress(address);
client.Endpoint.Behaviors.Add(new InspectorBehavior());
Does anyone know if there's a way to capture exactly what is going out (verbatim), without any post-processing as shown above?
You can configure WCF Message Logging in your App.config to log messages at transport level.
This should capture the message as late as possible:
For outgoing messages, logging happens immediately after the message leaves user code and immediately before the message goes on the wire.
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add name="messages"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="c:\logs\messages.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging
logEntireMessage="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="false"
logMessagesAtTransportLevel="true"
maxMessagesToLog="1000"
maxSizeOfMessageToLog="1000000"/>
</diagnostics>
</system.serviceModel>
You can capture exact content via custom MessageEncoder, unlike message inspectors (IDispatchMessageInspector / IClientMessageInspector) it sees original byte content including any malformed XML data.
However it's a bit harder to implement this approach. You have to wrap a standard textMessageEncoding as custom binding element and adjust config file to use that custom binding.
Also you can see as example how I did it in my project - wrapping textMessageEncoding, logging encoder, custom binding element and config.

Include Schema Type in WSDL file

I created a WSDL by hand that has only one operation with no input parameter and no output parameter.
I am getting following error when I try to create a client from this WSDL:
Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporter
Error: Schema with target namespace 'http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/' could not be found.
XPath to Error Source: //wsdl:definitions[#targetNamespace='http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/']/wsdl:portType[#name='GAMEAssociateIntf'] C:\toolbox\BlueTest\BloodRedTest\BloodRedTest\Service
The types (to be used in the client) need to be generated from the XML present in the WSDL. I think, while adding Service Reference, the tool is failing to create it due to some error in the XML. The xsd seems to be the issue.
What change need to be done in the WSDL to create the proxy ?
Note: I am trying to include the xml types defined in WSDL itself. [I don't need a separate file for schema defenition]
WSDL
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="GAMEAssociate"
targetNamespace="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"
xmlns:tns="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsp="http://www.w3.org/ns/ws-policy"
>
<types>
<xsd:schema>
</xsd:schema>
<xsd:element name="myData">
<xsd:complexType />
</xsd:element>
<xsd:element name="myDataResponse">
<xsd:complexType />
</xsd:element>
</types>
<message name="getAllVicePresidentsRequest">
<part element="tns:myData" name="getAllVicePresidentsRequest"/>
</message>
<message name="getAllVicePresidentsResponse">
<part element="tns:myDataResponse" name="getAllVicePresidentsResponse"/>
</message>
<portType name="GAMEAssociateIntf">
<operation name="getAllVicePresidents">
<input message="tns:getAllVicePresidentsRequest"/>
<output message="tns:getAllVicePresidentsResponse"/>
</operation>
</portType>
<binding name="GAMEAssociateIntfBinding" type="tns:GAMEAssociateIntf">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="getAllVicePresidents">
<soap:operation soapAction="http://www.xmlns.mycompany.com/GAME/wsdl/AssociateIntf/1.4/getAllVicePresidentsRequest"
style="document"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="GAMEAssociate">
<port binding="tns:GAMEAssociateIntfBinding" name="GAMEAssociateSOAP">
<soap:address location="http://localhost:8014/associateservice/GAMEAssociate.svc"/>
</port>
</service>
</definitions>
REFERENCES:
WSDL - no input - best practice
What does this WCF error mean: "Custom tool warning: Cannot import wsdl:portType"
Writing a WSDL 1.1 Web Service Contract by Hand
Writing Contract-First Web Services
generate wcf server code from wsdl files
How to get wsdl input and output names to appear
Inline Schema
Hand rolled SOAP request
I spent some time trying to find out issues. Your <types> section of the wsdl is incorrect, it should be like below. With this, I can now generate client side artefacts in Java.
<types>
<schema targetNamespace="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"
xmlns="http://www.w3.org/2001/XMLSchema">
<element name="myData">
<complexType/>
</element>
<element name="myDataResponse">
<complexType/>
</element>
</schema></types>
UPDATE
Based on the conversation below. Added a nillable attribute.
<element name="myDataResponse" nillable="true">
<complexType/>
</element>
Request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/">
<soapenv:Header/>
<soapenv:Body>
<ns:myData/>
</soapenv:Body>
</soapenv:Envelope>
Response
<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">
<myDataResponse xsi:nil="true" xmlns="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"/>
</s:Body>
</s:Envelope>
wsi.org has a set of validation tools, which would certainly be worth trying.
The added benefit of using these tools is that you can then actually claim compliance with the WSDL specification(s).
I referred Inline Schema. Thanks to #Indoknight also. The working contract WSDL is given below.
Note1: The xsd prefix and targetNamespace is there in the schema that is used inside wsdl type
Note2: nillable="true" is applied for the type used in the response message
Note3: If you are getting following exception, make sure that the soapAction is exactly same in the contract WSDL and the wsdl generated from WCF service. Some tools used for generating Service code may modify the SOAP action at it’s own will.
Faultcod: a:ActionNotSupported
Faultstring: The message with Action 'http://www.xmlns.mycompany.com/GAME/wsdl/AssociateIntf/1.4/getAllVicePresidentsRequest' 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).
SOAP Action - Contract WSDL
<soap:operation soapAction="http://www.xmlns.mycompany.com/GAME/wsdl/AssociateIntf/1.4/getAllVicePresidentsRequest"
SOAP Action - Generated WSDL
<soap:operation soapAction="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/IGAMEAssociateIntf/getAllVicePresidents"
Contract WSDL
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="GAMEAssociate"
targetNamespace="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"
xmlns:tns="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsp="http://www.w3.org/ns/ws-policy"
>
<types>
<xsd:schema targetNamespace="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<xsd:element name="myData">
<xsd:complexType />
</xsd:element>
<xsd:element name="myDataResponse" nillable="true">
<xsd:complexType />
</xsd:element>
</xsd:schema>
</types>
<message name="getAllVicePresidentsRequest">
<part element="tns:myData" name="getAllVicePresidentsRequest"/>
</message>
<message name="getAllVicePresidentsResponse">
<part element="tns:myDataResponse" name="getAllVicePresidentsResponse"/>
</message>
<portType name="GAMEAssociateIntf">
<operation name="getAllVicePresidents">
<input message="tns:getAllVicePresidentsRequest"/>
<output message="tns:getAllVicePresidentsResponse"/>
</operation>
</portType>
<binding name="GAMEAssociateIntfBinding" type="tns:GAMEAssociateIntf">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="getAllVicePresidents">
<soap:operation soapAction="http://www.xmlns.mycompany.com/GAME/wsdl/AssociateIntf/1.4/getAllVicePresidentsRequest"
style="document"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="GAMEAssociate">
<port binding="tns:GAMEAssociateIntfBinding" name="GAMEAssociateSOAP">
<soap:address location="http://localhost:8014/associateservice/GAMEAssociate.svc"/>
</port>
</service>
</definitions>
Request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/">
<soapenv:Header/>
<soapenv:Body>
<ns:myData/>
</soapenv:Body>
</soapenv:Envelope>
Response
<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">
<myDataResponse xsi:nil="true" xmlns="http://www.xmlns.mycompany.com/GAME/service/Associate/1.1/"/>
</s:Body>
</s:Envelope>

WCF tracing in code does not follow MessageLogging settings

I need to use WCF tracing in my application but it needs to be controlled from code as much as possible.
IT was suggested that I install the following sections in my app.config file:
<configuration>
<system.serviceModel>
<diagnostics>
<messageLogging
maxMessagesToLog="100"
logEntireMessage="true"
logMessagesAtServiceLevel="true"
logMalformedMessages="true"
logMessagesAtTransportLevel="true">
</messageLogging>
</diagnostics>
</system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel" >
<listeners>
<add type="System.Diagnostics.DefaultTraceListener" name="dummy"/>
</listeners>
</source>
</sources>
</system.diagnostics>
</configuration>
Then the following code could be used to get the trace running as needed:
BindingFlags privateMember = BindingFlags.NonPublic | BindingFlags.Instance;
BindingFlags privateStaticMember = privateMember | BindingFlags.Static;
Type type = Type.GetType("System.ServiceModel.DiagnosticUtility, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
MethodInfo[] mi = type.GetMethods(privateStaticMember);
// invoke InitializeTracing
object diagnosticTrace = mi.FirstOrDefault(e => e.Name == "InitializeTracing").Invoke(null, null);
if (diagnosticTrace != null)
{
// get TraceSource
Type type2 = Type.GetType("System.ServiceModel.Diagnostics.DiagnosticTrace, SMDiagnostics, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
PropertyInfo pi = type2.GetProperty("TraceSource", privateMember);
TraceSource traceSource = pi.GetValue(diagnosticTrace, null) as TraceSource;
// clear all listeners in the trace source
traceSource.Listeners.Clear();
// add listener to trace source
XmlWriterTraceListener listener = new XmlWriterTraceListener("mylogfile".svclog");
listener.TraceOutputOptions = TraceOptions.Timestamp | TraceOptions.Callstack;
traceSource.Attributes["propagateActivity"] = "true";
traceSource.Switch.ShouldTrace(TraceEventType.Verbose | TraceEventType.Start);
traceSource.Listeners.Add(listener);
// enable tracing
type.GetProperty("Level", privateStaticMember).SetValue(null, SourceLevels.All, null);
Trace.AutoFlush = true;
This works fine up to a point, the main problem being that the messagelogging settings in the system.servicemodel section of the app.config file are being ignored.
Is there anything that can be done to solve this problem?
I can't comment on all of your code because I have not used System.Diagnostics in this way before (programmatically configuring the WCF communication tracing), but if your intent on this line:
traceSource.Switch.ShouldTrace(TraceEventType.Verbose | TraceEventType.Start);
Is to set the level of tracing that you want, I think that you should be using the Switch.Level property instead. ShouldTrace is for asking if a given TraceSource would trace, given the input flags.
traceSource.Switch.Level = SourceLevels.Verbose | SourceLevels.ActivityTracing;
Note that according to this link, it is possible to configure apparently reasonable settings and yet the activity id might not be propogated correctly. Read it carefully. It may or not apply to your situation.
You need to enable MessageLogging by defining a trace source as indicated in this MSDN Library page. So, you need this extra bit in your app.config in the sources section:
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add type="System.Diagnostics.DefaultTraceListener" name="dummy"/>
<remove name="Default" />
</listeners> </source>
The message logging settings don't apply to the System.ServiceModel trace source.

Why does the XmlRoot attribute gets ignored in WCF and how to overcome this

We've observed that when we expose a WCF service which uses classes decorated with various xml serialisation attributes, despite the fact that we use the XmlSerializerFormat attribute on the interface any XmlRoot attribute on any of the operation's parameters gets completely ignored.
The namespace of the parameters is always that of the service and not what we specify.
This is causing us problems as it does not seem to be backwards compatible with ASMX and also because we're using BizTalk, and need to have tighter control over the shape of the XML's exchanged.
A few questions then -
Anybody knows what is the rationale
behind this decision?
Anybody knows
how this is happening? I was under
the impressions that WCF, with the
XmlSerializerFormat attribute, uses
the XmlSerialiser to serialise the
types, which would suggest XmlRoot
should be taken into account, how
come this is not the case? (is it
only due to the fact that, taking
the SOAP envelope into account, the
parameter is not root?)
Most
importantly - anybody knows if
there's a way to 'force the issue' -
i.e. get the parameters to be of the
namespace of our choosing?
I've seen this post, but I don't believe it is relevant to my question -
As per Wagner Silveira's request - the contracts I used to test this are -
[ServiceContract(Namespace = "http://servicecontract"),
XmlSerializerFormat(Style = OperationFormatStyle.Document)]
public interface ITestService
{
[OperationContract]
MyOtherType MyTestMethod(MyType obj);
}
// Composite class for DCS and XMLS
[Serializable, XmlType, XmlRoot(Namespace = "http://datacontract")]
public class MyType
{
[XmlAttribute]
public string StringValue { get; set; }
}
// Composite class for DCS and XMLS
[Serializable, XmlType, XmlRoot(Namespace = "http://datacontract")]
public class MyOtherType
{
[XmlAttribute]
public string OtherStringValue { get; set; }
}
I assume you're using SOAP as the message format. In this case, the object you're serializing is not the root of the XML, the soap envelope is. So it makes sense that the XmlRoot would be ignored. By default WCF will create a message contract for you and name the response and it has the namespace of the service. What you can do is create your own message contract to have full control over SOAP.
Create the following two classes:
[MessageContract]
public class MyTestMethodRequest
{
[MessageBodyMember( Namespace = "http://datacontract" )]
public MyType MyType;
}
[MessageContract]
public class MyTestMethodResponse
{
[MessageBodyMember( Namespace = "http://datacontract" )]
public MyOtherType MyOtherType;
}
Then change the signature of your service operation to the following.
[OperationContract]
public MyTestMethodResponse MyTestMethod( MyTestMethodRequest request )
{
return new MyTestMethodResponse {
MyOtherType = new MyOtherType {
OtherStringValue = "bar"
}
};
}
Now if you example the SOAP messages you should see the following:
Request
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"
s:mustUnderstand="1">http://servicecontract/TestService/MyTestMethod</Action>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyTestMethodRequest xmlns="http://servicecontract">
<MyType StringValue="foo" xmlns="http://datacontract" />
</MyTestMethodRequest>
</s:Body>
</s:Envelope>
Response
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyTestMethodResponse xmlns="http://servicecontract">
<MyOtherType OtherStringValue="bar" xmlns="http://datacontract" />
</MyTestMethodResponse>
</s:Body>
</s:Envelope>
I don't know why WCF ignores XmlRoot, so I can't answer that part of your question. But I do have a couple ways to solve the problem.
start with WSDL first.
If you have a particular set of XML namespaces you would like to apply to the messages that get sent and receieved, use WSDL and XML Schema to explicitly specify them.
Then, generate the Server-side stub code, or the client-side proxy code, directly from that WSDL via the svcutil.exe tool.
use a custom ServiceHost
The other option open to you, described at this link, is to use a custom ServiceHost that overrides WCF's decision to disregard the XmlRoot or XmlType attributes on message types.
If you choose to go for the WSDL-First approach, the WSDL should look like this:
<?xml version="1.0" encoding="utf-8" ?>
<definitions
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="urn:The-Service-namespace"
xmlns:tns="urn:The-Service-namespace"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:n0="urn:The-Request-namespace"
xmlns:n1="urn:The-Response-namespace"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
elementFormDefault= "unqualified"
>
<types>
<s:schema targetNamespace="urn:The-Request-namespace" >
<s:complexType name="Type1">
<s:sequence>
<s:element name="x" minOccurs="1" maxOccurs="1" type="s:string"/>
</s:sequence>
</s:complexType>
<s:element name="Type1" type="n0:Type1" />
</s:schema>
<s:schema targetNamespace="urn:The-Response-namespace" >
<s:complexType name="Type2">
<s:sequence>
<s:element name="x" minOccurs="1" maxOccurs="1" nillable="false" type="s:string"/>
<s:element name="y" minOccurs="1" maxOccurs="1" nillable="false" type="s:int"/>
<s:element name="z" minOccurs="1" maxOccurs="1" nillable="false" type="s:boolean" />
</s:sequence>
</s:complexType>
<s:element name="Type2" type="n1:Type2" />
</s:schema>
</types>
<message name="RequestMessage">
<part name="inPart1" element="n0:Type1" />
</message>
<message name="ResponseMessage">
<part name="outPart1" element="n1:Type2" />
</message>
<portType name="PortTypeName">
<operation name="Method1">
<input message="tns:RequestMessage" />
<output message="tns:ResponseMessage" />
</operation>
</portType>
<binding name="InterfaceName" type="tns:PortTypeName">
<soap:binding
transport="http://schemas.xmlsoap.org/soap/http"
style="rpc" />
<operation name="Method1">
<soap:operation soapAction="" style="document" />
<input> <soap:body use="literal" /> </input>
<output> <soap:body use="literal" /> </output>
</operation>
</binding>
</definitions>
This WSDL is very simple - it defines a single operation, with a single request message and a single response message.
Notice there are three xml namespaces:
urn:The-Service-namespace
used for the element that wraps the request and response - the first element inside the <SOAP:body>
urn:The-Request-namespace
used for the element wrapped inside that request wrapper, which gets deserialized into an instance of Type1.
urn:The-Response-namespace
used for the element wrapped inside that response wrapper, which gets deserialized into an instance of Type2.
If your web services interface is more complicated, has more operations and consequently more request and response message types, you can add more namespaces, if you like, for all those additional types.