SOAP serialization ignoring object but including its data members in C# - c#

I have an existing SOAP method which has a huge number of parameters, e.g.
[OperationContract]
public ResultObject DoSomeAction(string a, string b, DateTime c, OtherEnum d,
string e, string f, ....)
Which results in
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<DoSomeAction>
<xs:element minOccurs="0" maxOccurs="1" name="a" type="xs:string"/>
...
</DoSomeAction>
</soapenv:Body>
</soapenv:Envelope>
Ideally I'd like to factor out these into an object without changing the SOAP WSDL, so that the code could look more like
[OperationContract]
public ResultObject DoSomeAction(RequestObject request)
[DataContract]
public class RequestObject
{
[DataMember]
public string a { get; set; }
...
}
However I can't seem to find the correct way to add an attribute to serialize only the properties of Request Object and not the object itself.
Are there any way to define the Request Object that that the resulting SOAP will end up the same?

You can do this using MessageContract instead of DataContract this gives you more control over the message. For this to work you have to also wrap the result object in a message contract object to get exactly the same.
Here is a sample of your objects changed to message contracts.
[MessageContract(WrapperName="DoSomeActionResponse")]
public class ResponseMessage
{
[MessageBodyMember(Name="DoSomeActionResult")]
public ResultObject ResultObject { get; set; }
}
[MessageContract(WrapperName = "DoSomeAction")] // renames the element to DoSomeAction
public class RequestObject
{
[MessageBodyMember]
public string a { get; set; }
[MessageBodyMember]
public string b { get; set; }
[MessageBodyMember]
public DateTime c { get; set; }
[MessageBodyMember]
public int d { get; set; }
[MessageBodyMember]
public string e { get; set; }
[MessageBodyMember]
public string f { get; set; }
}
And the operation contract becomes like this.
[OperationContract]
ResponseMessage DoSomeAction(RequestObject requestObject);
For more details on how to use message contracts check also the official documentation.
Due to your comment I post also the requests for both method declaration so you see it is the same.
Your original:
[OperationContract]
ResultObject DoSomeAction(string a, string b, DateTime c, int d, string e, string f);
And the SOAP request and response look like this:
<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/IService1/DoSomeAction</Action>
</s:Header>
<s:Body>
<DoSomeAction xmlns="http://tempuri.org/">
<a i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
<b i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
<c>2020-04-05T02:04:00</c>
<d>0</d>
<e i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
<f i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
</DoSomeAction>
</s:Body>
</s:Envelope>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<DoSomeActionResponse xmlns="http://tempuri.org/">
<DoSomeActionResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfService1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:ResultValue>12</a:ResultValue>
</DoSomeActionResult>
</DoSomeActionResponse>
</s:Body>
</s:Envelope>
And the request and response from the message contact methods so you see it is the same:
<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/IService1/DoSomeAction</Action>
</s:Header>
<s:Body>
<DoSomeAction xmlns="http://tempuri.org/">
<a i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
<b i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
<c>2020-04-05T02:03:00</c>
<d>0</d>
<e i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
<f i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
</DoSomeAction>
</s:Body>
</s:Envelope>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<DoSomeActionResponse xmlns="http://tempuri.org/">
<DoSomeActionResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfService2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:ResultValue>11</a:ResultValue>
</DoSomeActionResult>
</DoSomeActionResponse>
</s:Body>
</s:Envelope>

Related

Change XML Tag Prefix SOAP

I am trying to create a SOAP message with the prefix. however, I am having trouble setting the namespace correctly. I have been trying for days and tried many suggestions I found online, but none seem to work. I am hoping some of you can help me.
What I'm getting is
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Transaction xmlns="http://tempuri.org/">
<bankingTransaction>
<operation parameterOrder="string">
<fault />
<fault />
</operation>
<transactionDate>dateTime</transactionDate>
<amount>int</amount>
</bankingTransaction>
</Transaction>
</soap:Body>
</soap:Envelope>
& what I actually need is
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<res:Transaction xmlns="res:http://tempuri.org/">
<res:bankingTransaction>
<res:operation parameterOrder="string">
<res:fault />
<res:fault />
</res:operation>
<res:transactionDate>dateTime</res:transactionDate>
<res:amount>int</res:amount>
</res:bankingTransaction>
</res:Transaction>
</soap:Body>
</soap:Envelope>
& My MassageContact is
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public Operation operation;
[MessageHeader] public DateTime transactionDate;
[MessageBodyMember] private unit sourceAccount;
[MessageBodyMember] public int amount;
}
Please Help me to add prefix with my XML Elements.
Thanks
You probably need to do something like this:
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://tempuri.org/")]
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public Operation operation;
[MessageHeader] public DateTime transactionDate;
[MessageBodyMember] private unit sourceAccount;
[MessageBodyMember] public int amount;
}
I am not sure how you are serializing your objects, but something like this will add the prefix:
XmlSerializerNamespaces x = new XmlSerializerNamespaces();
x.Add("res", "http://tempuri.org/");
add the XmlSerializerNamespaces to you serialization process maybe? It's hard to say without seeing what else you are doing. All your contracts/classes in that namespace probably need this attribute: [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://tempuri.org/")]
We could create a MessageFormatter to customize the message format, you could refer to the following official tutorial.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/extending/custom-message-formatters
Here is an example about how to do this.
https://stackoverflow.com/questions/31595770/c-sharp-wcf-global-namespaces-royal-mail/31597758#31597758
http://vanacosmin.ro/Articles/Read/WCFEnvelopeNamespacePrefix

SoapFormatter - how to include namespaces?

I have a blocker which I cannot solve. Issue is in auto-generated SOAP request built by SoapFormatter class. I'm trying to communicate with WCF service and pass some data. I've implemented class which I am trying to serialize to soap request.
[Serializable]
public class MySoapClass: ISerializable
{
public string Username{ get; set; }
public string Password{ get; set; }
public int Data3 { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.FullTypeName = "ThisIsNameIWantInSoap";
info.AddValue("Username", Username);
info.AddValue("Password", Password);
info.AddValue("Data3", Data3);
}
}
I'm using MemoryStream and MySoapClass object in SoapFormatter. I am getting soap string this way Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Position)
Generated soap string does not work, request is delivered, but I am getting "authentication error", just like WCF service could not extract any data from request.
This is auto-generated soap string:
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:ThisIsNameIWantInSoap id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/assem/http://tempuri.org/">
<Username id="ref-1">username</Username>
<Password id="ref-2">password</Password>
<Data3>10</Data3>
</a1:ThisIsNameIWantInSoap>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
After copying soap string to SoapUI and adding namespace tag to every parameter, everything works fine. I am getting proper response from WCF service.
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:ThisIsNameIWantInSoap id="ref-1" xmlns:a1="http://tempuri.org/">
<a1:Username id="ref-1">username</a1:Username>
<a1:Password id="ref-2">password</a1:Password>
<a1:Data3>10</a1:Data3>
</a1:ThisIsNameIWantInSoap>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
My questions are:
-How to auto-generate soap string which includes "a1:" namespace tag in every parameter?
-(Answered) How to change "a1:" namespace to"somethingElse:"?

WCF Soap response is wrapping properties

This WCF response seems to be wrapping the property WhoAmIResult more than once.
Response:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<WhoAmIResponse xmlns="http://tempuri.org/">
<WhoAmIResult xmlns:a="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:WhoAmIResult>your name here</a:WhoAmIResult>
</WhoAmIResult>
</WhoAmIResponse>
</s:Body>
</s:Envelope>
Expectation:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<WhoAmIResponse xmlns="http://tempuri.org/">
<a:WhoAmIResult>your name here</a:WhoAmIResult>
</WhoAmIResponse>
</s:Body>
</s:Envelope>
Here's the code behind:
[ServiceContract]
public interface IService
{
[OperationContract]
WhoAmIResponse WhoAmI(string s);
}
[DataContract]
public class WhoAmIResponse
{
[DataMember]
public string WhoAmIResult { get; set; }
}
public class Service : IService
{
public WhoAmIResponse WhoAmI(string s)
{
return new WhoAmIResponse
{
WhoAmIResult = s
};
}
}
I just can't figure out what I need to do to get to this response, without exceeding wrappers. I must not have WhoAmIResult twice in the answer.
You can try setting setting Namespace to empty
[DataContract(Namespace = "")]
public class WhoAmIResponse
{
[DataMember]
public string WhoAmIResult { get; set; }
}
When I took out the namespace as #STORM told me to, I've got this response:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<WhoAmIResponse xmlns="http://tempuri.org/">
<WhoAmIResult xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<WhoAmIResult xmlns="">your name result</WhoAmIResult>
</WhoAmIResult>
</WhoAmIResponse>
</s:Body>
</s:Envelope>
The namespace wasn't there but there was still the wrapper.
After some experiments I noticed that WhoAmIResult could be the name of a string response not a object. So I removed WhoAmIResponse object and put a simple string response. The code ended up like this:
public class MyService : IMyService
{
public string WhoAmI(string s)
{
return s;
}
}
And now I get the expected response:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<WhoAmIResponse xmlns="http://tempuri.org/">
<WhoAmIResult>your name result</WhoAmIResult>
</WhoAmIResponse>
</s:Body>
</s:Envelope>
The problem was that I didn't need to declare another object to give the answer. There seems to be a convention for the response XML like [MethodName]Response and [MethodName]Result.
Thanks #STORM, I'm ashamed.

SOAP Web service custom soap xml response

This is my XML response soap (WS in c# ".asmx") :
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<TestrequestResponse xmlns="http://tempuri.org/">
<TestrequestResult>
<name>filename.xml</name>
<code>30</code>
</TestrequestResult>
</TestrequestResponse>
</soap:Body>
</soap:Envelope>
I want to have a response like this :
<?xml version="1.0" encoding="utf-8"?>
<Response result=”KO”>
<name>filename.xml</name>
<code>30</code>
</Response>
how can I do this?
Thank you very much for your help.
edit :
[WebMethod]
public Response Testrequest()
{
var r = new Response();
r.name = "30";
r.code = "0";
return r;
}
object response :
public class Response
{
public string name { get; set; }
public string code { get; set; }
}
You may do SOAP message formatting ( https://msdn.microsoft.com/en-us/library/dkwy2d72%28v=vs.100%29.aspx ), but creating a simple web page returning the desired format instead of calling a SOAP service is easier.

Manipulating WCF header details

I am building a client to some STS service and for more than one day now I am trying to add a Header to a WCF message. In my call to RequestSecurityToken I have to include a UsernameToken.
I'm not sure how to accomplish that. For the moment I defined an endpoint behavior and a message inspector (took me long enough to discover those...). In the BeforeSendRequest() of the latter I create an object of the custom class 'Security' which derives from MessageHeader. Security includes an instance of UsernameToken.
public class MessageInspector : IClientMessageInspector {
public object BeforeSendRequest(ref Message request, IClientChannel channel) {
Security uns = new Security();
uns.UsernameToken = new UsernameToken();
// ...
var Header = new MessageHeader<Security>(uns);
var untyped = Header.GetUntypedHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
request.Headers.Add(untyped);
return null;
}
}
public class Security : MessageHeader {
public UsernameToken UsernameToken = new UsernameToken();
public override string Name {
get { return "Security"; }
}
public override string Namespace {
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
}
public class UsernameToken {
public String Username = "";
public Password Password = new Password();
}
This is what is being serialised
<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">urn:RequestSecurityToken</Action>
<Security xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<UsernameToken xmlns="http://schemas.datacontract.org/2004/07/Tarifrechner.Kfz">
<Password>
<Type>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText</Type>
<password>******</password>
</Password>
<Username>******</Username>
</UsernameToken>
</Security>
</s:Header>
<s:Body />
</s:Envelope>
Especially the namespace of UsernameToken seems to be wrong. I know it comes from the data contract serialization but i need a different namespace.
This is what I would like the serialised data to look like
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="...">
<soap:Header>
<Security xmlns:q1="http://www.bipro.net/namespace" xsi:type="q1:UserNameSecurity"
xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<UsernameToken>
<Username>******</Username>
<Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">******</Password>
</UsernameToken>
</Security>
<wsa:Action>urn:RequestSecurityToken</wsa:Action>
<wsse:Security>
<wsu:Timestamp wsu:Id="Timestamp-b9dd599d-5901-451d-8321-6a309091f273">
<wsu:Created>2012-03-11T16:02:56Z</wsu:Created>
<wsu:Expires>2012-03-11T16:07:56Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</soap:Header>
<soap:Body>
<RequestSecurityToken xmlns="http://schemas.xmlsoap.org/ws/2005/02/trust">
<TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</TokenType>
<RequestType>
http://schemas.xmlsoap.org/ws/2005/02/trust/Issue
</RequestType>
</RequestSecurityToken>
</soap:Body>
</soap:Envelope>
Is my approach about right? And how can I manipulate things like the namespace of a header detail or whether data is being serialised as attribute or element?
Update
As Ladislav already noted, I don't have to implement classes like UsernameToken myself. I did that only because my knowledge of WCF is so limited.
By now, I discovered, that WS2007HttpBinding, configured to use SecurityMode.TransportWithMessageCredential and with EstablishSecurityContext set to false produces almost the XML I am looking for. How should I have known that?
But there's one problem left: My request has an empty body element, where the request I want to produce, features a RequestSecurityToken element inside the body element. Does anyone know, how I can achieve that?
Using EstablishSecurityContext = true helps but at the same time changes my Soap-Action from the desired "urn:RequestSecurityToken" into the non-working "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/SCT".
I appreciate any answer!
Thanks a lot!
Björn
One alternative way is to define a MessageContract type for your request, which allows you to define what shows up in the header and body of the SOAP message and adjust the namespace used. For example, consider the following service definition:
[ServiceContract]
public interface IMyService
{
[OperationContract]
MyResponse DoSomething(MyRequest request);
}
public class MyService : IMyService
{
public MyResponse DoSomething(MyRequest request)
{
return new MyResponse()
{
Details = "Service did something awesome.",
Timestamp = DateTime.Now
};
}
}
[MessageContract(IsWrapped = true, WrapperNamespace = "http://myservice/messages/")]
public class MyRequest
{
[MessageHeader(Namespace = "http://myservice/security")]
public string TokenThingy
{
get;
set;
}
}
[MessageContract(IsWrapped = true, WrapperNamespace = "http://myservice/messages")]
public class MyResponse
{
[MessageBodyMember]
public string Details
{
get;
set;
}
[MessageBodyMember]
public DateTime Timestamp
{
get;
set;
}
}
Sending a request produces the following 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/IMyService/DoSomething</Action>
<h:TokenThingy xmlns:h="http://myservice/security">fda</h:TokenThingy>
</s:Header>
<s:Body>
<MyRequest xmlns="http://myservice/messages/" />
</s:Body>
</s:Envelope>
And a response from the service looks like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<MyResponse xmlns="http://myservice/messages">
<Details xmlns="http://tempuri.org/">Service did something awesome.</Details>
<Timestamp xmlns="http://tempuri.org/">2012-05-04T17:04:36.5980424-04:00</Timestamp>
</MyResponse>
</s:Body>
</s:Envelope>
There are a few different ways to add headers to a message depending on how you need to control the header content and where you need to insert the header.
In your application code you can create an OperationContextScope around the request in order to change some of the request properties. Inside an OperationContextScope, you have a valid instance of OperationContext.Current, which allows manipulation of the message headers through the OutgoingMessageHeaders collection. Use of this method deeply bakes control of the headers into the application code. You would have to be responsible for copying the appropriate code wherever it was needed.
These two links (from someone on the WCF team) talk about this in greater detail with a ton of code samples:

Categories