How to use both MessageContract and CollectionDataContract in the same class? - c#

I have the following code for a web service:
[ServiceContract]
public interface IService1
{
[OperationContract]
WrapperResponse GetStringCollection(CustomRequest req);
}
[MessageContract(WrapperNamespace = Constants.NamespaceTem)]
public class CustomRequest
{
[MessageBodyMember]
public StringCollection CustomStrings
{
get; set;
}
}
[CollectionDataContract(ItemName = "CustomString")]
public class StringCollection : List<string>
{
public StringCollection(): base() { }
public StringCollection(string[] items) : base()
{
foreach (string item in items)
{
Add(item);
}
}
}
The service accepts the following SOAP Request:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<CustomRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="CustomNamespace">
<CustomStrings xmlns:d4p1="http://schemas.datacontract.org/2004/07/WcfService1">
<d4p1:CustomString>text1</d4p1:CustomString>
<d4p1:CustomString>text2</d4p1:CustomString>
</CustomStrings>
</CustomRequest>
</s:Body>
</s:Envelope>
However, it should accept the following SOAP Request (Without the "CustomStrings"-tag):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<CustomRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="CustomNamespace">
<d4p1:CustomString>text1</d4p1:CustomString>
<d4p1:CustomString>text2</d4p1:CustomString>
</CustomRequest>
</s:Body>
</s:Envelope>
If I don't use MessageContract, like this:
[OperationContract]
WrapperResponse GetStringCollection(StringCollection CustomRequest);
I am able to achieve the following XML, which is close to what I want:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetStringCollection xmlns="CustomNamespace">
<CustomRequest xmlns:d4p1="http://schemas.datacontract.org/2004/07/WcfService1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:CustomString>text1</d4p1:CustomString>
<d4p1:CustomString>text2</d4p1:CustomString>
</CustomRequest>
</GetStringCollection>
</s:Body>
</s:Envelope>
But the "GetStringCollection"-tag is present, which is what MessageContract helps me remove.
So I need both the MessageContract and the CollectionDataContract, but if I do the following:
[MessageContract]
[CollectionDataContract(ItemName = "CustomString")]
public class StringCollection : List<string>
{
public StringCollection(): base() { }
public StringCollection(string[] items) : base()
{
foreach (string item in items)
{
Add(item);
}
}
}
I get an exception:
"Exception Details: System.InvalidOperationException: The type WcfService1.StringCollection defines a MessageContract but also derives from a type System.Collections.Generic.List`1[System.String] that does not define a MessageContract. ÿAll of the objects in the inheritance hierarchy of WcfService1.StringCollection must defines a MessageContract."
So the question is: Is there a way to use both the MessageContract and CollectionDataContract in the top class? If not, how can I then accept the wanted request?

Message Contract IS NOT a DataContract or CollectionDataContract!
See DataContract-Vs-MessageContract-in-WCF on codeproject
You can say, that you want to declare a contract for transfering data between consumer and provider. This contract can be CollectionDataContract(List) OR DataContract(class) OR MessageContract(which used for detailed message control)

Related

Deserialize the soap xml response in a .netcore app

I have to call a soap service from a C# .net core app and get the results in a class that I can use to do some logic. I did the soap request and the remote call works fine, but now I have to deserialize the xml soap response into a class. Here's an example of the response:
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://some_domain/soap/ApplicationServices">
<SOAP-ENV:Body>
<ns1:preparelabelsResponse xmlns:ns1="http://some_domain/soap/ApplicationServices">
<return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:PrepareReturn[1]">
<item xsi:type="tns:PrepareReturn">
<clientref xsi:type="xsd:string">2015/0418/001</clientref>
<pclid xsi:type="xsd:string"></pclid>
<error xsi:type="xsd:string">Invalid timestamp 20191018105727</error>
</item>
</return>
</ns1:preparelabelsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Since I'm using Visual Studio 2017 I tried adding a connected service in my .netcore app using the WCF Web Service Reference Provider and then call the method. The call failed with the message: Wrong XML format, that was probably generated by the server. Doing the same steps in a .net framework app worked fine though, and I got the proper response. So I suspect something is different in the .netcore wcf provider.
My second approach was to create the soap request manually and then parse the soap response. For constructing the request I have an elegant solution, but for parsing the response I don't. I tried to use the classes that were generated by the wcf provider, but the xml deserialization didn't work. I then tried to change the attributes to get it right, but didn't help. I added below those classes:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
[System.ServiceModel.MessageContractAttribute(WrapperName="preparelabelsResponse", WrapperNamespace="encoded", IsWrapped=true)]
public partial class preparelabelsResponse
{
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="", Order=0)]
public PrepareReturn[] #return;
public preparelabelsResponse()
{
}
public preparelabelsResponse(PrepareReturn[] #return)
{
this.#return = #return;
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.1")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.SoapTypeAttribute(Namespace="http://some_domain/soap/ApplicationServices")]
public partial class PrepareReturn
{
private string clientrefField;
private string pclidField;
private string errorField;
/// <remarks/>
public string clientref
{
get
{
return this.clientrefField;
}
set
{
this.clientrefField = value;
}
}
/// <remarks/>
public string pclid
{
get
{
return this.pclidField;
}
set
{
this.pclidField = value;
}
}
/// <remarks/>
public string error
{
get
{
return this.errorField;
}
set
{
this.errorField = value;
}
}
}
And the xml deserialization:
var soapResponse = XDocument.Load(sr);
XNamespace myns = "http://some_domain/soap/ApplicationServices";
var xml = soapResponse.Descendants(myns + "preparelabelsResponse").FirstOrDefault().ToString();
var result = Deserialize<preparelabelsResponse>(xml);
...
public static T Deserialize<T>(string xmlStr)
{
var serializer = new XmlSerializer(typeof(T));
T result;
using (TextReader reader = new StringReader(xmlStr))
{
result = (T)serializer.Deserialize(reader);
}
return result;
}
So what I could do is to strip the xml away of all the namespaces and attributes and deserialize it to a simple class, but that is not an elegant solution for my problem. What I want is to be able to create/decorate my classes in such a way that the deserialization will work without any altering of the actual xml contents.

C# SOAP Service - Remove method node and have header in soapheader node

I'm tasked with creating a web service that conforms to a particular wsdl and I haven't used SOAP or asmx before.
When I create a request in SoapUI I get the following structure, which is the same as the client will be using to send requests. (using placeholder names)
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:par="http://www.foo.com/schemas/method">
<soapenv:Header>
<par:SOAPHeaderRequest>
<par:ApplicationID>ID</par:ApplicationID>
</par:SOAPHeaderRequest>
</soapenv:Header>
<soapenv:Body>
<par:Body>
</par:Body>
</soapenv:Body>
</soapenv:Envelope>
However, when I'm trying to create the service I have this structure:
<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>
<Method xmlns="http://www.foo.com/schemas/method">
<request>
<SOAPHeaderRequest>
<ApplicationID>string</ApplicationID>
</SOAPHeaderRequest>
<body>
<Property>string</Property>
</body>
</request>
</Method>
</soap:Body>
</soap:Envelope>
I'd like to know how to remove the Method node wrapper, and how to move the SOAPHeaderREquest into a soap:Header.
Here's my sample code:
interface and objects
[ServiceContract(Namespace = "http://www.foo.com/schemas/method")]
public interface IServiceContract
{
[XmlSerializerFormat]
[OperationContract]
ResponseObject Method(RequestObject request);
}
[System.Serializable()]
[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://www.foo.com/schemas/method")]
[MessageContract(IsWrapped = false)]
public class RequestObject
{
[System.ServiceModel.MessageHeader(Namespace = "http://www.foo.com/schemas/method")]
public SOAPHeaderRequest SOAPHeaderRequest;
[System.ServiceModel.MessageBodyMember(Namespace = "http://www.foo.com/schemas/method", Order = 0)]
public Body body;
public RequestObject()
{
}
public RequestObject(SOAPHeaderRequest SOAPHeaderRequest, Body body)
{
this.body = body;
}
}
[System.Serializable()]
[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://www.foo.com/schemas/method")]
[MessageContract(IsWrapped = false)]
public class ResponseObject
{
[System.ServiceModel.MessageHeader(Namespace = "http://www.foo.com/schemas/method")]
public SOAPHeaderResponse SOAPHeaderResponse;
[System.ServiceModel.MessageBodyMember(Namespace = "http://www.foo.com/schemas/method", Order = 0)]
public Body body;
}
[System.Serializable()]
public class Body
{
public string Property { get; set; }
}
asmx
[WebService(Namespace = "http://www.foo.com/schemas/method")]
[WebServiceBinding(ConformsTo = WsiProfiles.None)]
public class M5NapaPartUpdateService : WebService, IServiceContract
{
[WebMethod]
[SoapMethod(SoapAction = "method")]
public ResponseObject Method(RequestObject request)
{
return new ResponseObject();
}
}
Let me know if there's anything else you'd need.
Thanks!
WSDL distinguishes between two message styles:
document and RPC.
The message style affects the contents of the SOAP Body:
Document style: The SOAP Body contains one or more child elements called parts. There are no SOAP formatting rules for what the body contains; it contains whatever the sender and the receiver agrees upon.
**RPC style:**RPC implies that SOAP body contains an element with the name of the method or operation being invoked. This element in turn contains an element for each parameter of that method/operation.
Your wsdl is written in Document Literal style.
If you are using service contract then I believe you are using WCF framework to write service code.
You can specify below parameters to make WSDL as you expect.
[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples"), XmlSerializerFormat(Style = OperationFormatStyle.Document,
Use = OperationFormatUse.Literal)]
Reference- https://www.ibm.com/support/knowledgecenter/en/SSB27H_6.2.0/fa2ws_ovw_soap_syntax_lit.html
Hope this helps.

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.

WCF: How to deserialize parameters of a SOAP message with different namespaces?

I am new to WCF and Stackoverflow. I am trying to handle a SOAP (1.2) request from an existing client.
The message would be like:
<s:Body>
<ns1:MyMethod>
<ns1:Parameter1> A string value </ns1:Parameter1>
<ns2:Parameter2> Another string value </ns2:Parameter2>
</ns1:MyMethod>
</s:Body>
Here is my server side code:
[SerivceContract(Namespace = "ns1...")]
public class IMyService
{
[OperationContract(Action="http://the action url")]
void MyMethod(string Parameter1, string Parameter2);
}
I can get "Parameter1" deserialized correctly, but "Parameter2" is always null. I suppose it was because of different namespaces (ns1 vs ns2).
Any helps?
It is highly unusual to have to create a service to fit the requests a client is already sending.
This is like trying to purchase a new computer which supports the drivers for a 15 year old printer.
Solution: just buy a new printer.
The client should be consuming the service, not the other way around.
Appreciate this does not directly answer your question and may be outside the scope of a solution.
This is an old question but I stumbled into it when trying to find the answer to a similar question.
The reason for which the Parameter1 was deserialized and not Parameter2 is probably because both fields had no namespace definition so they inherited the namespace from their parent (MyMethod). This is also explained on this thread.
For the current case, you would need to use the custom XmlSerializerFormat and add the ns2 namespace to the Param2:
[ServiceContract(Namespace = "http://ns1.com")]
[XmlSerializerFormat]
public interface IOpenInvoiceInterface
{
[OperationContract]
MyMethod Test(MyMethod req);
}
public class MyMethod
{
[MessageBodyMember]
public string Param1 { get; set; }
[MessageBodyMember(Namespace = "http://ns2.com")]
public string Param2 { get; set; }
}
With this setup, the following call will be deserialized as expected:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://ns1.com" xmlns:ns2="http://ns2.com">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ns1:MyMethod >
<ns1:Param1>abc</ns1:Param1>
<ns2:Param2>cde</ns2:Param2>
</ns1:MyMethod>
</s:Body>
</s:Envelope>

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