WCF Service ref string param that accepts XML? - wcf

I'm writing a WCF service that receives events. It's to an agreed standard so I've got to stick to the service definition, and I don't control the data the clients send. Again this is to an agreed standard although the data can vary.
Here's one of the methods on my service:
complexType ErrorEvent(int requestId, complexType returnValue, ref string errorInfo)
Clients send XML in the errorInfo string that my function will manipulate and return.
The data I get is like this (full SOAP request):
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ErrorEvent xmlns="http://blah">
<requestId>1</requestId>
<returnValue>
<returnCode>0</returnCode>
</returnValue>
<errorInfo>
<ErrorMessage>An error message</ErrorMessage>
<DefaultTask><!-- Complex data --></DefaultTask>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<ExtraMessage>hello</ExtraMessage>
<ExtraMessage>world</ExtraMessage>
</errorInfo>
</ErrorEvent>
</s:Body>
</s:Envelope>
However, when I try and run this I get this error (edited):
The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter errorInfo. The InnerException message was 'There was an error deserializing the object of type System.String. End element 'errorInfo' from namespace '' expected. Found element 'ErrorMessage' from namespace ''.
So my question is, is there any way I achieve what I want to do without altering the signature of my method? For example adding attributes to my service etc? Or do I need to intercept the message?
Thanks for any pointers.

Does it have to be passed as a string? You know you can also receive XmlElement and XElements in WCF?
Are you generating the SOAP request yourself? Could you use a CDATA section, i.e.
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ErrorEvent xmlns="http://blah">
<requestId>1</requestId>
<returnValue>
<returnCode>0</returnCode>
</returnValue>
<errorInfo>
<![CDATA[<ErrorMessage>An error message</ErrorMessage>
<DefaultTask><!-- Complex data --></DefaultTask>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<Task><!-- Complex data --></Task>
<ExtraMessage>hello</ExtraMessage>
<ExtraMessage>world</ExtraMessage>]]>
</errorInfo>
</ErrorEvent>
</s:Body>
</s:Envelope>

I didn't find a way to make this work and as #luksan says, it's a misuse by a client to send unescaped XML to a string parameter.
The workaround I adopted was to make a class that implements IClientMessageInspector, IDispatchMessageInspector and IEndpointBehavior to intercept, check and modify any messages that were incorrect.
If I could have changed the interface, another workaround would have been to accept XmlNode[] instead of string.

Related

How can I get WebMock to match a body containing specific XML data using hash_including?

I have a Ruby (2.6.5) on Rails (5.2.3) application which is already successfully using Savon (2.12) to interact with FlightAware's FlightXML 2 API via SOAP/WSDL. However, I'm running into an issue while attempting to write Minitest tests for this functionality, using WebMock (3.7.6) to stub out the API requests.
My application (via Savon) makes a request including an XML body like the following example:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:FlightXML2="http://flightxml.flightaware.com/soap/FlightXML2" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<FlightXML2:AirportInfoRequest>
<FlightXML2:airportCode>KDFW</FlightXML2:airportCode>
</FlightXML2:AirportInfoRequest>
</env:Body>
</env:Envelope>
I would like to use a with statement so that the stub only responds to a request having a specific airport code in the element. For example, if I'm looking for "KDFW", then a request containing <FlightXML2:airportCode>KDFW</FlightXML2:airportCode> should match, but a request containing <FlightXML2:airportCode>CYYZ</FlightXML2:airportCode> should not.
Right now, I have it working by including the XML tag in a regular expression:
icao_code = "KDFW"
response_body = "[the response I want]"
WebMock.stub_request(:post, "http://flightxml.flightaware.com/soap/FlightXML2/op").
with(body: /<FlightXML2:airportCode>#{icao_code}<\/FlightXML2:airportCode>/).
to_return(status: 200, body: response_body)
This feels like a kludge, especially when the documentation for WebMock mentions that I can use hash_including to match XML tags. No matter what I try, though, I get the typical WebMock unregistered request error, indicating that I don't match the request. Here are the with statements that I've tried so far:
with(body: hash_including({airport_code: icao_code})
with(body: hash_including({"airportCode": icao_code})
with(body: hash_including({"FlightXML2:airportCode": icao_code})
How can I match a specific XML element without having to resort to putting the XML tag in a regular expression?
Match the request against a block:
stub_request(:post, "http://flightxml.flightaware.com/soap/FlightXML2/op")
.with do |request|
# do something
end.to_return(status: 200, body: response_body)
The block code could look something like:
request_xml = Nokogiri::XML(request.body)
request_xml.xpath("//FlightXML2:airportCode").any? do |node|
node.content == icao_code
end
This approach has an additional advantage in that you can insert a debugger into the above code block and figure out why your test case isn't working. You can see Webmock docs here.

Manually calling a web service with a SOAP message

I'm trying to call a SOAP-based WCF web service by creating an HTTP request by hand. I need to do this because I'm trying to implement this in an environment that will have HTTP access, but not the WCF service client stuff. It will actually be implemented in a different language, but I'm trying to do a proof of concept in C# first.
The WCF service has a simple function that takes a string address and then returns a complex object with geocoding information about the address. Right now, I'm just looking for it to return a proper response as a string. As it is, it returns HTML describing the WSDL discovery, so the call isn't working.
I pulled the SOAP message from an actual functioning service call (I wrote some code to intercept and extract the SOAP message before it went out).
So the trick now is just getting the rest of the HTTP to work. So my code is as follows:
string soap = "<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/IGeoCode/GetLocation</Action>" +
" </s:Header>" +
" <s:Body>" +
" <GetLocation xmlns=\"http://tempuri.org/\">" +
" <address>1600 Pennsylvania Ave NW, Washington, DC 20500</address>" +
" </GetLocation>" +
" </s:Body>" +
" </s:Envelope>";
var obj = new XMLHTTP60();
obj.open("POST", #"http://MyServer:4444/GeoCode.svc");
obj.setRequestHeader("Content-Type", "text/xml");
obj.setRequestHeader("SOAPAction", "http://MyServer:4444/GeoCode.svc");
obj.setRequestHeader("Content-Length", soap.Length.ToString());
obj.send(soap);
string stat = obj.statusText;
string str = obj.responseText;
string resp = obj.getAllResponseHeaders();
What I expect back is something along the lines of:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<GetLocationResponse xmlns="http://tempuri.org/">
<GetLocationResult xmlns:a="http://schemas.datacontract.org/2004/07/GeoCodeSvc" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
...
Lots of stuff here
...
</GetLocationResult>
</GetLocationResponse>
</s:Envelope>
</s:Body>
Instead, I'm getting a 400 (Bad Request) status.
I think your problem is with the SOAPAction statement. You don't want to point that to your service. Instead, it should be whatever the WSDL calls the end point.
If you're feeling adventurous, the end point is typically one of the last entries in the WSDL. I can't recall the specific term they use, but it should be obvious.
HTH, Jim

For a BizTalk WCF-SQL adapter in Solicit-Response Send port. How to let adapter handle the response data like XmlPolling?

For a SP using For Xml returns a xml as response, If this sp was consumed by a WCF receive location, we can specify the Polling as XmlPolling to let adapter keep the responsed xml "as-is". But for a wcf-sql Solicit-Response Send port, It seems no same way to do it. Currently, the best result I can get is let adapter treat the xml response as a CDATA. Like below:
<usp_MySPResponse xmlns="http://schemas.microsoft.com/Sql/2008/05/TypedProcedures/dbo">
<StoredProcedureResultSet0>
<StoredProcedureResultSet0 xmlns="http://schemas.microsoft.com/Sql/2008/05/ProceduresResultSets/dbo/usp_MySP">
<UnNamedColumn0><![CDATA[ <!-- The XML content of sp returned goes here -->
....
Assuming the Stored Procedure uses the FOR XML clause, there are two binding properties you must set for the result to be recognized as Xml:
XmlStoredProcedureRootNodeName
XmlStoredProcedureRootNodeNamespace
It's these two properties that tell the Adapter it's a FOR XML Stored Procedure. These are essentially the same as the legacy SQL Adapter.
Details here: http://msdn.microsoft.com/en-us/library/dd787898.aspx
There is no way around this so your BizTalk Schema must match these values.
Reading the link https://learn.microsoft.com/en-us/biztalk/adapters-and-accelerators/adapter-sql/execute-stored-procedures-having-a-for-xml-clause-in-sql-server-using-biztalk carefully, it says that the request schema must be generated selecting from "Procedures", undeniably not "TypedProcedures". The request message will therefore be in the namespace http://schemas.microsoft.com/Sql/2008/05/Procedures/dbo which is not explicitly mentioned in
https://learn.microsoft.com/en-us/biztalk/adapters-and-accelerators/adapter-sql/message-schemas-for-procedures-and-functions but the action for a "for xml"-typed procedure is specified there. To summarize: A request message is thus:
<YOURSPNAME xmlns="http://schemas.microsoft.com/Sql/2008/05/Procedures/dbo" />
and the port configuration action header is
<BtsActionMapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Operation Name="YOURSPNAME" Action="XmlProcedure/dbo/YOURSPNAME" />
</BtsActionMapping>
Note the discrepancy between the request message's namespace and the operation action, only the action has "Xml" in it. And don't forget to specify the (FOR XML) binding parameters, as answered by Johns-305.

ksoap2 and WCF complex types

I'm sending this request to my WCF webservice with a Android client using ksoap2:
<v:Envelope xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d="http://www.w3.org/2001/XMLSchema" xmlns:c="http://schemas.xmlsoap.org/soap/encoding/" xmlns:v="http://schemas.xmlsoap.org/soap/envelope/"><v:Header />
<v:Body>
<HelloComplex xmlns="http://tempuri.org/" id="o0" c:root="1">
<complex i:type="n0:SampleComplexType" xmlns:n0="http://tempuri.org/">
<Value i:type="d:string">Hello!</Value>
</complex>
</HelloComplex>
</v:Body>
</v:Envelope>
and receive back this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<s:Fault>
<faultcode xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">a:DeserializationFailed</faultcode>
<faultstring xml:lang="pt-BR">
The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:complex. The InnerException message was 'Error in line 1 position 363. Element 'http://tempuri.org/:complex' contains data from a type that maps to the name 'http://tempuri.org/:SampleComplexType'. The deserializer has no knowledge of any type that maps to this name. Consider using a DataContractResolver or add the type corresponding to 'SampleComplexType' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.
</faultstring>
</s:Fault>
</s:Body>
</s:Envelope>
But when my other application (AspNew MVC) call this :
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<HelloComplex xmlns="http://tempuri.org/">
<complex xmlns:a="http://schemas.datacontract.org/2004/07/IssueCenter.Core" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Value>C#VALUE!</a:Value>
</complex>
</HelloComplex>
</s:Body>
</s:Envelope>
ant it works.
What can I do to fix my Android client requests?
Notes: I know about the namespace diference and I already try to make the same.
The service doesn't know how to deal with the "ComplexType" type attribute in the XML, so you basically need to tell the service-side serializer what it means. You can do it in a few ways: either you add a DataContractResolver that tells the serializer what this unknown type ID means, or you can add the type (if it exists on the service side) to the list of "known types", so that the service knows exactly what to do with it. I think you should use a DataContractResolver.
In the version 4.0 of the framework, WCF introduced the data contract resolver. Instead of defining the set of known types “statically” (e.g. by specifying the types directly in a KnownTypeAttribute like you did until .NET 4), the data contract resolver provides some hooks which allow you to specify, at the moment when the object is being serialized or deserialized, a mapping between the CLR type and the name / namespace in the XML which will be used to represent this “unknown” type. To do this, you just inherit from the DataContractResolver class and provide your mapping logic.
I think this is the better solution for you, because you likely don't want to create a dummy type on the server side just to deal with this problem, which is what you'd have to do if you were to use KnownTypeAttribute. Only thing to remember is that a resolver makes the serialization slower than using the “standard” known types feature (since known types are static, they can be cached and the calls don’t need to be made all the time), so be aware that when using the resolver, the additional functionality comes at a price in terms of execution time.

WCF server method - returning DataSet kills output parameters

Looking for some collective wisdom. Here is the situation. This is using Visual Studio 2008.
We have a simple WCF contract exposing the following method:
DataSet ExecuteQuery (out string someStuff);
This method is implemented as:
public DataSet ExecuteQuery (out string someStuff)
{
someStuff = "abc";
return new DataSet(); // simplified
}
Client side proxy is generated by svcutil, and appears to be correct.
Problem:
someStuff is always null on the client side.
Observations:
Same problem appears on our build machine.
Returning a string instead of DataSet makes client proxy receive proper value of out parameter:
public string ExecuteQuery(out string someStuff) ...
Removing the client proxy completely and regenerating it doesn't help.
Client definitely receives both DataSet and the "out" string, as seen in its trace log:
<ExecuteQueryResponse xmlns="http://tempuri.org/">
<ExecuteQueryResult>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded"></xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"></diffgr:diffgram>
</ExecuteQueryResult>
<someStuff>abc</someStuff>
</ExecuteQueryResponse>
Here is how the client proxy generates the code:
[System.ServiceModel.OperationContractAttribute
(Action="http://tempuri.org/IQueryContract/ExecuteQuery",
ReplyAction="http://tempuri.org/IQueryContract/ExecuteQueryResponse")]
[System.ServiceModel.XmlSerializerFormatAttribute()]
System.Data.DataSet ExecuteQuery(out string someStuff);
We use netTcpBinding
Do we need to do anything special in order to return DataSet and out parameters? Anyone has come across anything similar?
Thanks!!!!
WCF, as defined by SOA, must not transport an object (state and behavior), only the state and strucuture of an object. BinaryFormatter, and SoapFormatter are inadequate for SOA because:
1) Requires both ends to share type
2) cannot be used for contracts
3) both formatters require streams
DataContractSerializer only shares types not contracts. It adds support for XML readers and writers, only captures state according to the schema, and deos not support IFormatter.
All of which I took from my WCF course that I took from Juval Lowry :)
Richard
I had [XmlSerializerFormat] at the top of my contract interface. Removing it fixed the problem, as WCF starts using the default [DataSerializerFormat]. Thanks Darin for point it out.
Still not sure why XmlSerializerFormat wouldn't work.