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:
Related
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.
I need to send a createShipmentRequest() to DHLWebSerivces and my problem is to add a security header to the soap request.
Edited to add the DHL documentation:
I'm trying the last solution proposed here How To Pass Soap Header When WSDL Doesn't Define It?.
public partial class SecurityHeader : SoapHeader
{
public string Username { get; set; }
public string Password { get; set; }
}
public partial class gblExpressRateBook
{
public SecurityHeader securityHeader = new SecurityHeader() {
EncodedMustUnderstand = "1", Username = "*****", Password = "*****" };
protected override XmlWriter GetWriterForMessage(SoapClientMessage message,
int bufferSize)
{
message.Headers.Add(securityHeader);
return base.GetWriterForMessage(message, bufferSize);
}
}
Here is the result in Fiddler:
Edited to add the entire first line "soap:Envelope".
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:Header
SecurityHeader [ xmlns=http://scxgxtt.phx-dc.dhl.com/euExpressRateBook/ShipmentMsgRequest ]
...
And here is what I should have according to a code sample of DHL:
Edited to add the indentation of the code.
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:glob="http://scxgxtt.phx-dc.dhl.com/glDHLExpressLabel/providers/globalLabel">
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-5" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>*****</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">*****</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">*****************</wsse:Nonce>
<wsu:Created>2010-02-12T17:40:39.124Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
Edited to add SOAPUI request done via Fiddler:
Sent request via Fiddler:
Received response via Fiddler:
Can someone please help me build this "wsse:Security" header?
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)
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.
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>