Force WCF to create an xml namespace alias in client proxy - c#

I used the Add Service Reference feature to create a proxy to an external web service.
By default, the WCF client is producing SOAP messages where the message body has namespace decorations that look like this:
<s:Body>
<BankingTransaction xmlns="http://tempuri.org/">
<amount>0</amount>
</BankingTransaction>
</s:Body>
I need the message body to look like this instead
<s:Body>
<bb:BankingTransaction xmlns:bb="http://tempuri.org/">
<amount>0</amount>
</bb:BankingTransaction>
</s:Body>
The distinction is the "bb" xml namespace alias. The web service that I'm trying to consume requires that the xml namespace for the message payload be aliased. And the default behavior the WCF client is to define the namespace as the DEFAULT namespace. I've searched high and low for a configuration / decoration solution to this problem and haven't found it. Barring a configuration solution, I'll have to inspect and alter each SOAP message after it is serialized. #lame.
Is there a simple solution here?

The solution to this problem is to create a custom MessageInspector (via IClientMessageInspector) to inspect and alter the SOAP messages that the WCF client proxy produces, prior to sending them over the wire. The basis for this solution is articulated in Steven Cheng's post, "[WCF] How to modify WCF message via custom MessageInspector", with further background from Kirk Evan's post, "Modify Message Content with WCF".
I used the code from Steven's post to wire up the custom MessageInspector infrastructure. Then I modified his Transformf2() method, which operates only on the <Body> portion of the SOAP message, to suit my particular needs. In my case, as described in the original question, I needed to define and use an alias for my target web service XML namespace, xmlns="http://tempuri.org", above.
To do this I must
obtain a reference to the operation node, <BankingTransaction>,
which will always be the first (and only) child of <Body>
remove the attribute that sets the default namespace to the target
namespace
set the prefix (namespace alias) for the node
The modified Transform2() code that does this is below:
private static Message Transform(Message oldMessage)
{
//load the old message into XML
var msgbuf = oldMessage.CreateBufferedCopy(int.MaxValue);
var tmpMessage = msgbuf.CreateMessage();
var xdr = tmpMessage.GetReaderAtBodyContents();
var xdoc = new XmlDocument();
xdoc.Load(xdr);
xdr.Close();
// We are making an assumption that the Operation element is the
// first child element of the Body element
var targetNodes = xdoc.SelectNodes("./*");
// There should be only one Operation element
var node = (null != targetNodes) ? targetNodes[0] : null;
if (null != node)
{
if(null != node.Attributes) node.Attributes.RemoveNamedItem("xmlns");
node.Prefix = "bb";
}
var ms = new MemoryStream();
var xw = XmlWriter.Create(ms);
xdoc.Save(xw);
xw.Flush();
xw.Close();
ms.Position = 0;
var xr = XmlReader.Create(ms);
//create new message from modified XML document
var newMessage = Message.CreateMessage(oldMessage.Version, null, xr);
newMessage.Headers.CopyHeadersFrom(oldMessage);
newMessage.Properties.CopyProperties(oldMessage.Properties);
return newMessage;
}
}

Related

C# SOAP client: sending generic XmlNode request

I have a C# project where I added a SOAP service reference, using the integrated visual studio functionality (right click -> add -> service reference)
The client classes are generated correctly without errors. However, the various methods of the service only accept a generic System.Xml.XmlNode as an input, rather than a structured object.
This should not be a problem in theory, since I have the complete XML file with the query that I need to perform. So I tried doing it like this:
NSIStdV20ServiceSoapClient client = new NSIStdV20ServiceSoapClient();
var getAllDataFlowQuery = File.ReadAllText(#"Query\get_all_dataflow.xml"); //file containing the query
XmlDocument doc = new XmlDocument();
doc.LoadXml(getAllDataFlowQuery);
var dataStructures = client.QueryStructure(doc); //this method accepts a System.Xml.XmlNode as parameter
However, this doesn't work, throwing
System.ServiceModel.FaultException: 'Error due to a non correct client message'
I thought initially that the query was incorrect, but I tried to perform the exact same query using SoapUI and it works perfectly! I even tried doing it with the exact XML returned by doc.InnerXml (just to be sure che XmlDocument object was not modifying the XML) and it works.
So basically it's only when calling the method from C# that it doesn't work.
If you want to try it out yourself, the service is freely accessible, the WSDL is here:
http://sdmx.istat.it/SDMXWS/NsiStdV20Service.asmx?WSDL
and you should try to call the QueryStructure method with the following payload:
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://ec.europa.eu/eurostat/sri/service/2.0"><soapenv:Header /><soapenv:Body><web:QueryStructure><!--Optional:--><web:Query><RegistryInterface xsi:schemaLocation="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message SDMXMessage.xsd" xmlns="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message" xmlns:common="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/common" xmlns:compact="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/compact" xmlns:cross="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/cross" xmlns:generic="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/generic" xmlns:query="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/query" xmlns:structure="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/structure" xmlns:registry="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/registry" xmlns:utility="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/utility" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Header><ID>JD014</ID><Test>true</Test><Truncated>false</Truncated><Name xml:lang="en">Trans46302</Name><Prepared>2001-03-11T09:30:47-05:00</Prepared><Sender id="BIS" /></Header><QueryStructureRequest resolveReferences="false"><registry:DataflowRef /></QueryStructureRequest></RegistryInterface></web:Query></web:QueryStructure></soapenv:Body></soapenv:Envelope>
As I said, this works perfectly in SoapUI, but doesn't work when calling the client method from C#
Well, it seems that the client generated by visual studio, even tho it accepts a XmlNode as input, creates some of the required outer structure itself (to be precise: all the outer nodes with the soapenv and web namespaces).
Which means I had to strip down the input XML to:
<?xml version="1.0" encoding="UTF-8"?><RegistryInterface xsi:schemaLocation="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message SDMXMessage.xsd" xmlns="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message" xmlns:common="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/common" xmlns:compact="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/compact" xmlns:cross="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/cross" xmlns:generic="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/generic" xmlns:query="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/query" xmlns:structure="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/structure" xmlns:registry="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/registry" xmlns:utility="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/utility" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Header><ID>JD014</ID><Test>true</Test><Truncated>false</Truncated><Name xml:lang="en">Trans46302</Name><Prepared>2001-03-11T09:30:47-05:00</Prepared><Sender id="BIS" /></Header><QueryStructureRequest resolveReferences="false"><registry:DataflowRef /></QueryStructureRequest></RegistryInterface>

How Do I Change XML Prefix For WCF Client Request c#?

Due to requirements from a web service that I have no control over, I need to change my ns prefixes from the defaults of "s" and "h". The service provider has provided a .wsdl file for my service reference, however, they do not appear to have an online wsdl for metadata exchange. I have emailed them and they confirmed that the prefixes have to be specific values (right or wrong on their part, I have to make this work).
Can someone please tell me the easiest way to change these values? Is there something I can modify in Reference.cs? Do I have to implement some sort of message formatting logic into my requests to change the values just prior to sending out the request?
Every element in the XML has to have a prefix of a specific value (2 values total), or the WS will not accept the request and simply returns HTML in the response. When I copy the request captured in Fiddler, over to SOAP UI and update the prefixes to what they require, the request works fine when executed from Soap UI. I'm simply not finding any simple solutions to this seemingly simple problem for c# VS 2017 development platform.
Also worth noting, when I load the .wsdl file into Soap UI, Soap UI generates all of the ws operations and requests with the correct, desired prefixes that I need in my c# app. How does Soap UI know the exact prefixes to generate in the requests?
I need to modify this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:FooListHeader xmlns:h="http://foo.foo.com/Hello" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<h:FooList>
<h:FooStatement>
<h:Title>Foo</h:Title>
<h:Value>123</h:Value>
</h:FooStatement>
</h:FooList>
</h:FooListHeader>
</s:Header>
<s:Body>
<GetFooRequestType xmlns="http://foo.foo.com/Hello">
<MessageRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ConFooRequest/>
</MessageRequest>
</GetFooRequestType>
</s:Body>
</s:Envelope>
To something like this:
<soapenv:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:foo="http://foo.foo.com/Hello">
<soapenv:Header>
<foo:FooListHeader xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<foo:FooList>
<foo:FooStatement>
<foo:Title>Foo</foo:Title>
<foo:Value>123</foo:Value>
</foo:FooStatement>
</foo:FooList>
</foo:FooListHeader>
</soapenv:Header>
<soapenv:Body>
<foo:GetFooRequestType xmlns="http://foo.foo.com/Hello">
<foo:MessageRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<foo:ConFooRequest/>
</foo:MessageRequest>
</foo:GetFooRequestType>
</soapenv:Body>
</soapenv:Envelope>
Example c# code:
public GetYaddaYaddaResponseType CheckConnection()
{
Yadda client = new Yadda();
var msgRequest = new GetYaddaYaddaResponseType { ConnectionConfirmationRequest = new ConfirmConnectRequestType() };
List<Statement> statementList = new List<Statement>
{
new Statement { Name = "Yo", Value = "123" },
new Statement { Name = "Hey", Value = "123" },
};
var header = new StatementHeader
{
StatementList = statementList.ToArray()
};
var response = client.GetYaddaYaddaMessage(ref header, msgRequest);
return response;
}

How to serialize an object into string\xml with its headers

I'm using a third side web service client (created by using the "Add service reference") in order to retrieve some data.
After filling the web service objects with proper data we need to add some data to the headers (encrypted password and some other predefined data)
Then, we are serializing every request sent to the web service, using the standard .net XmlSerializer.
However, in the result of the serialization I don't see the headers of the request. I've searched for a long time and couldn't find any way to "print" them as well.
Here is some example code:
Ibooking proxy = new BookingManager();
/* Init proxy Data...*/
GetAvailabilityRequest request = new GetAvailabilityRequest();
/*Fill more data on the request...*/
GetAvailabilityResponse response = proxy.GetAvailability(request); //Send request to the web service
var xmlString2 = response.Serialize(); //only body, no headers in the XML
/* Extension class to Serialize any object */
public static class ExtensionUtil
{
public static string Serialize<T>(this T value)
{
try
{
XmlSerializer xmlserializer = new XmlSerializer(typeof(T));
var stringWriter = new StringWriter();
using (var writer = XmlWriter.Create(stringWriter))
{
xmlserializer.Serialize(writer, value);
return stringWriter.ToString();
}
}
catch (Exception ex)
{
throw new Exception("An error occurred", ex);
}
}
}
I've excluded the code that adds more data to the request since it's long and complicated (need to implement IEndpointBehavior and IClientMessageInspector to "catch" the request before we send it) - but currently as a workaround I put a BreakPoint on the Message object and convert it into string using Visual Studio. In this way I do see the headers but obviously this is bad practice since I want it to be automated in the serialization.
I would like to see an example of how you are adding these headers.
In most web services the message body is the part that is serialized into XML or JSON - the headers are not.
You may be able to inspect the service call by using Fiddler and a proxy implemented by a small change in your web.config as described in this article: http://weblog.west-wind.com/posts/2008/Mar/14/Debugging-Http-or-Web-Services-Calls-from-ASPNET-with-Fiddler.
The short version of this is to add the following to your web.config or app.config:
<system.net>
<defaultProxy>
<proxy proxyaddress="http://127.0.0.1:8888" />
</defaultProxy>
</system.net>
Download and run Fiddler while calling the service and you should see and be able to inspect the call in Fiddler.
If you want to inspect and/or modify the headers within your code base could look into implementing IClientMessageInspector or IDispatchMessageInspector. Here are a couple articles on the topic:
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector(v=vs.100).aspx
http://weblogs.asp.net/paolopia/writing-a-wcf-message-inspector
Here is an implementation I did. I didn't need access the headers, but rather to modify the xml namespaces created by the service client, but it should give you an idea on how to do the implementation: How can I create custom XML namespace attributes when consuming a legacy SOAP service?
OperationContext is your friend here. Use an OperationContextScope to wrap the call to the service, then use OperationContext.Current to get at all the hidden goodies you need.
https://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontextscope(v=vs.110).aspx
Note that you'll need to know the specific types of the headers you want to get at, and I've had some trouble getting at the values, rather than just the names, of headers if they're not marked as serializable when using XmlSerializer

Error updating Wcf message before hitting service using IDispatchOperationSelector

We are having to intercept a SOAP message before it hits our WCF service to perform the following steps:
Route the message to the correct method as the client is unable to provide us with a SOAPAction value.
Update the namespaces of the xml as the client is unable to add namespace information to the message.
The routing is not an issue, but we are having a problem with creating the message; once we recreate the message the body merely consists of "... Stream ...".
Before creating the message, the messageContent variable contains valid, correct xml.
private Message UpdateNamespaces(Message message, string methodName)
{
var memoryStream = new MemoryStream();
var xmlWriter = XmlWriter.Create(memoryStream);
message.WriteMessage(xmlWriter);
xmlWriter.Flush();
var messageContent = Encoding.UTF8.GetString(memoryStream.ToArray());
xmlWriter.Close();
// Update messageContent with corrected XML
memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(messageContent));
var xmlDictionaryReader = XmlDictionaryReader.CreateTextReader(memoryStream, new XmlDictionaryReaderQuotas());
var newMessage = Message.CreateMessage(xmlDictionaryReader, int.MaxValue, message.Version);
newMessage.Properties.CopyProperties(message.Properties);
return newMessage;
}
The messageContent is correct at the point at which we create the memoryStream, but as soon as I check the content of newMessage.ToString(), I'm getting the "... Stream ..." body content.
If anyone could help, I'd be very grateful as I'm out of ideas!
Many thanks
I think you are mixing behavior responsibilities.
IDispatchOperationSelector.SelectOperation is intended for routing decisions. It gives you a reference to the message, but the intention is to enable access to message properties. WCF does not expect the you to modify the message using this extension point. I can't say definitively, but the problem could be that simple.
If you want to alter the message namespace you should be using IDispatchMessageInspector. I suggest creating a second behavior for that task. Here's a nice Message Inspector example.

Envelop tag missing in xml object

I have a web application where the users can upload a specific XML file, then i need to convert the XML file to a object which is expect in a webservice.
Code:
var document = new XmlDocument();
document.Load(#"C:\Desktop\CteWebservice.xml");
var serializer =new XmlSerializer(typeof(OCTE));
var octe = (OCTE)serializer.Deserialize(new StringReader(document.OuterXml));
var serviceClient = new WSServiceClient();
serviceClient.InsertOCTE(octe);
I get this error when i try to deserialize the xml document as a OCTE object:
< Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/'> was not expect.
Obvious, the OCTE class doesn't have a Envelope property.
My question is, i need to include this tag in the OCTE class or i need to do something else in the client?
I can think of at least two different solutions to your problem, which seems to be caused by the fact that the XML file contain the full SOAP request, which will contain a SOAP Envelope, a SOAP Body element and potentially a SOAP header element.
The structure of SOAP body element is dependent on the SOAP Binding style, which most likely will be Document Literal Wrapped - for further details look at http://www.ibm.com/developerworks/webservices/library/ws-usagewsdl/index.html?ca=dat-
This means that the OCTE structure that you are looking for, might be embedded inside a request element, something like (document literal wrapped SOAP binding style)
<soap:envelope>
</soap:header>
<soap:body>
<insertOCTERequest>
<OCTE>
...
</OCTE>
</insertOCTERequest>
</soap:body>
</soap:envelope>
So to the possible solutions
Ensure that the CteWebService.xml is serialised to have the OCTE structure as it's root element.
Use etc. XPath to locate the OCTE element, and deserialise that instead.
var nsmgr = ...
var element = document.SelectSingleNode("//OCTE", nsmgr);
You only need to initialise a Xml NamespaceManager if the OCTE element belongs to a specified xml namespace, otherwise you can leave it out.

Categories