Anyone who have tried to do contract/schema first design of data contracts or tried to generate proxies using SVCUTIL.EXE in an interop scenario with e.g. Spring-WS, will have run into how WCF magically decides to use the old XmlSerializer rather than the new DataContractsSerializer (DCS). And deducing which parts of a schema that cause this is not easy with real-life contracts.
The XML schema rules/limitations for DCS is well documented, but the proxy generator will not tell you which of the rules your XSD/WSDL does not adhere to. Even worse, if you specify /serializer:DataContractSerializer and the rules are broken; the proxy will get generated without any error, but also without any messages and data transfer objects.
Note that the SVCUTIL switches /collectionType /ct and /reference /r only work with the DataContractSerializer, not the XmlSerializer. Thus, if the proxy generator falls back to the XmlSerializer, you will not get collections (List / BindingList / etc) for arrays nor any reuse of existing shared object types.
The only way I've found that will tell you why DCS is not used, is by using the /DCONLY option to extract the data contracts from the WSDL. Using this switch will disclose data contract (XSD) errors one at a time, but it is still better than not knowing why. Note that it will not disclose any errors related to the messages not being document/literal wrapped (WSDL).
The most common XSD schema lapsus is not wrapping arrays in a two-level complex type, one for the list of items and one for the items themselves. This is not very well documented in the DCS rules. The array XSD schema must follow this construct:
<xs:element name="ItemList" type="tns:ItemListType" />
<xs:complexType name="ItemListType">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Item" type="tns:ItemType" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ItemType">
<xs:sequence>
<xs:element name="Id" type="xs:long" />
<xs:element name="Value" nillable="true" type="xs:string" />
</xs:sequence>
</xs:complexType>
Note that there is no need to use the prefix ArrayOf on the list element name.
The reason for wrapping the array might seem quite silly when the message contains elements of only one type. However, look at this XML segment that contains elements of multiple types:
<root><a/><b/><b/><b/></root>
The DCS does not like a XSD schema where the children of an element varies in numbers and are not of the same type; it requires that all child elements that varies in numbers must have the same type, thus the wrapper element:
<root><a/><itemList><b/><b/><b/></itemList></root>
This way the <root> element contains a fixed number of different elements, the <a> element and the <itemList> element; while the <itemList> element contains a varying number of <b> elements only. In addition, an empty/null array can still be represented in the XML.
The other typical WSDL lapsus wrt DCS is about the message part definitions and the related operation input and output elements plus the related binding section. The important rule here is to remember to use only one part in each message (WS-I Basic Profile) and to set the message part attribute @name = "parameters" (by convention) to make the messages document/literal wrapped. Other @name conventions are "header" for message headers and "detail" for fault message parts to be used as wsdl:fault elements in operations.
This is an example that will allow you to use the DataContractSerializer:
<wsdl:message name="GetItemRequest">
<wsdl:part element="tns:GetItemRequestType" name="parameters" />
</wsdl:message>
<wsdl:message name="GetItemResponse">
<wsdl:part element="tns:GetItemResponseType" name="parameters" />
</wsdl:message>
<wsdl:message name="FaultDetailList">
<wsdl:part element="fault:FaultDetailList" name="detail" />
</wsdl:message>
<wsdl:portType name="InspectValues">
<wsdl:operation name="GetItem">
<wsdl:input message="tns:GetItemRequest" name="GetItemRequest" />
<wsdl:output message="tns:GetItemResponse" name="GetItemResponse" />
<wsdl:fault message="tns:FaultDetailList" name="FaultDetailList" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="InspectValuesSoap11" type="tns:InspectValues">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="GetItem">
<soap:operation soapAction="" />
<wsdl:input name="GetItemRequest">
<soap:body use="literal" /></wsdl:input>
<wsdl:output name="GetItemResponse">
<soap:body use="literal" /></wsdl:output>
<wsdl:fault name="FaultDetailList">
<soap:fault name="FaultDetailList" use="literal"/></wsdl:fault>
</wsdl:operation>
</wsdl:binding>
Note that if your message part element is a wrapped array, the referenced element must be marked as nillable. This is a SVCUTIL WSDL requriement, and applies even if using /DCONLY generates the contracts just fine.
If the generated DCS-based proxy interface contains operations that has no input or output messages, then your WSDL does not adhere to the above rules. The same is true if you get any "cannot import" errors or warnings related to wsdl:binding, wsdl:portType or wsdl:port. Note also that the element referenced as a message part must be a DCS allowable <xs:complexType> with an <xs:sequence> child (can also be empty). Read more about message schema rules in Handcrafting WCF friendly WSDLs.
There are some known issues with SVCUTIL /reference regarding the mismatches between some XSD schema types and .NET types (e.g. xs:date, xs:positiveinteger), which will make /reference fail as the WSDL types don't correspond with the referenced .NET data contract types: .NET has no Date only data type, just DateTime - thus xs:date becomes string. Also watch out for enumerations being represented as pure strings without any defined enum items. These issues will cause /reference to fail.
Note also that your custom shared collection types must also be referenced using /ct even if already added using /reference. Forgetting this will give your duplicate classes for all your [CollectionDataContractAttribute] lists defined in your data contracts.
As you might have noticed, using nillable elements might be required for /reference to work properly. Always try setting nillable="true" if you can't get any code generated when using the DataContractSerializer.
Pre .NET 3.5 SP1: All xs:string elements are required to be nillable. The same applies to your custom collection types.
I wish the DCS rules documentation had included more XSD schema examples, both for allowable and invalid schema constructs.
The /reference option is very important when using an architecture like CAB/SCSF where the service implementation (proxy) is separated from the service definition (interface). Then, it is important that the data contracts are included in the service interface assembly for reuse across business modules; i.e. the DTO objects can not be inside the proxy implementation assembly. The proxy itself is most likely wrapped in a service agent and should never be exposed in the DDD service or repository pattern interface.
If you can't get the /dconly plus /reference combo to work, then you're stuck with generating the contracts and proxy into classes in a single file using a .Service.Interface namespace and then extracting the proxy code into a separate file in a .Service namespace using e.g. a RegEx script, followed by some namespace using/imports refactoring for the extracted proxy code file so that it references the data contracts in the interface namespace.
Thursday, March 27, 2008
Tuesday, March 11, 2008
Tagged - Notebook Fetish
My friend Anders Norås tagged me on how I take notes. I have to admit that I like OneNote when I do research and information gathering, especially the free form text entry and the nice screen capture tool (Windows button + S) that also logs where you found the info.
However, whenever I interact with customers, e.g. taking meeting notes, doing SOA maturity interviews or capturing input from users; I have a standard blue, boring analogous A5 notebook and a pen, as sitting behind a laptop and typing away creates a barrier between me and the subjects.
Finally, I use my phone to enter an unsent text message should I happen to think of something ingeneously on my train commute. Which is very unlikely as it is only 14 minutes :)
Tag you're it: how do you enter notes:
Christian Weyer
Thomas Bruusgaard
However, whenever I interact with customers, e.g. taking meeting notes, doing SOA maturity interviews or capturing input from users; I have a standard blue, boring analogous A5 notebook and a pen, as sitting behind a laptop and typing away creates a barrier between me and the subjects.
Finally, I use my phone to enter an unsent text message should I happen to think of something ingeneously on my train commute. Which is very unlikely as it is only 14 minutes :)
Tag you're it: how do you enter notes:
Christian Weyer
Thomas Bruusgaard
Monday, March 03, 2008
WCF: WS-Security, SSL, TransportWithMessageCredentials, Message Logging
I'm currently on a project where we're using WCF to interop with services provided by Spring-WS hosted on WebSphere. The basic WS-Security UsernameToken credential type over a basicHttpBinding with SSL was chosen for authentication.
The first problem we got was that the self-signed SSL certificate was not accepted by SvcUtil.exe or "Add service reference":
The remote certificate is invalid according to the validation procedure. Could not establish trust relationship for the SSL/TLS secure channel. The remote certificate is invalid according to the validation procedure.
Sure enough, opening the URL in MSIE showed two certificate errors: issuer trust and certificate common name/site name mismatch. The first issue was easily solved, the latter was not. However, a workaround is to save the WSDL to a file and use that file as the input to SvcUtil.exe. Of course, the same invalid SSL certificate will come back and bite you when trying to use the proxy - more on this later.
I took the generated the proxy code and config output and merged it into my code. Unsurprisingly, the unit test failed with the message "no WS-Security headers found in request". I turned on message logging on both service and transport level using SvcConfigEditor.exe, remembering to set LogEntireMessage to true. Sure enough, no wsse:UsernameToken element in the soap:Header. Grief...
A lot of research lead me to the knowledge that WCF will by design not allow any sensitive data to be sent on an unsecure channel. Time to look at the generated output.config. After a few hours of frustrated config tuning and log inspections, the issue was solved: SvcUtil.exe generates config that combines Username credentials with Transport mode security. This looks OK and gives no config validation exception, but it is absolutely not correct. This is the correct config for using WS-Security over basicHttpBinding and SSL:
WCF will not put any wsse: headers into the request unless TransportWithMessageCredential is used as the security mode. In addition, add the username and password using the .Username and .Password properties of the proxy.ClientCredentials.UserName object.
After a while it occurred to me that the Spring-WS generated WSDL contained no WS-Policy statements, thus there was no guidance in the WSDL for creating the security artifacts for the proxy and config.
Now my request went through to WebSphere! But, how long was I in paradise? Until the response came back:
System.ServiceModel.Security.MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.
The problem is that WCF expects a timestamp in the response when it puts a wsse: timestamp in the request (see the transport level message log). I have not been able to turn the timestamp off using config for <basicHttpBinding>, but it can be done using a <customBinding> (see Kristian Kristensen's blog). Nicholas Allen provides code and a deeper explanation of the timestamp mechanism in his blog:
While you wait for the WebSphere folks to install a valid certificate, you can intercept the certificate validation and override any SSL policy violations:
At last my WCF client is able to invoke the web-service providing the expected WS-Security wsse:UsernameToken. What a wonderful interop world it is using WS-*.
Articles by IBM: Achieving Web services interoperability between the WebSphere Web Services Feature Pack and Windows Communication Foundation Part 1 and Part 2.
The first problem we got was that the self-signed SSL certificate was not accepted by SvcUtil.exe or "Add service reference":
The remote certificate is invalid according to the validation procedure. Could not establish trust relationship for the SSL/TLS secure channel. The remote certificate is invalid according to the validation procedure.
Sure enough, opening the URL in MSIE showed two certificate errors: issuer trust and certificate common name/site name mismatch. The first issue was easily solved, the latter was not. However, a workaround is to save the WSDL to a file and use that file as the input to SvcUtil.exe. Of course, the same invalid SSL certificate will come back and bite you when trying to use the proxy - more on this later.
I took the generated the proxy code and config output and merged it into my code. Unsurprisingly, the unit test failed with the message "no WS-Security headers found in request". I turned on message logging on both service and transport level using SvcConfigEditor.exe, remembering to set LogEntireMessage to true. Sure enough, no wsse:UsernameToken element in the soap:Header. Grief...
A lot of research lead me to the knowledge that WCF will by design not allow any sensitive data to be sent on an unsecure channel. Time to look at the generated output.config. After a few hours of frustrated config tuning and log inspections, the issue was solved: SvcUtil.exe generates config that combines Username credentials with Transport mode security. This looks OK and gives no config validation exception, but it is absolutely not correct. This is the correct config for using WS-Security over basicHttpBinding and SSL:
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" proxyCredentialType="None"realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
WCF will not put any wsse: headers into the request unless TransportWithMessageCredential is used as the security mode. In addition, add the username and password using the .Username and .Password properties of the proxy.ClientCredentials.UserName object.
After a while it occurred to me that the Spring-WS generated WSDL contained no WS-Policy statements, thus there was no guidance in the WSDL for creating the security artifacts for the proxy and config.
Now my request went through to WebSphere! But, how long was I in paradise? Until the response came back:
System.ServiceModel.Security.MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.
The problem is that WCF expects a timestamp in the response when it puts a wsse: timestamp in the request (see the transport level message log). I have not been able to turn the timestamp off using config for <basicHttpBinding>, but it can be done using a <customBinding> (see Kristian Kristensen's blog). Nicholas Allen provides code and a deeper explanation of the timestamp mechanism in his blog:
BindingElementCollection elements = proxy.Endpoint.Binding.CreateBindingElements();
elements.Find<SecurityBindingElement>().IncludeTimestamp = false;
proxy.Endpoint.Binding = new CustomBinding(elements);
While you wait for the WebSphere folks to install a valid certificate, you can intercept the certificate validation and override any SSL policy violations:
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;...
ServicePointManager.ServerCertificateValidationCallback =
delegate(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
//http://msdn2.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback.aspx
//ignore invalid SSL: allow this client to communicate with unauthenticated serversreturn true;
}
At last my WCF client is able to invoke the web-service providing the expected WS-Security wsse:UsernameToken. What a wonderful interop world it is using WS-*.
Articles by IBM: Achieving Web services interoperability between the WebSphere Web Services Feature Pack and Windows Communication Foundation Part 1 and Part 2.
Subscribe to:
Posts (Atom)