Soap request and response in c# - c#

I wrote c# code for a Soap XML request, I have verified this code generating an XML class.
My question is how to send request and receive response using c# code.
Please be kind with my simple or basic mistakes because I am a newbie to XML but your help would be really appreciated.
SOAP XML Request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ce="http://www." xmlns:os="http://www.domainname.com/schema/soap/v1" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<soapenv:Header />
<soapenv:Body>
<ce:message>
<ce:m_control>
<os:control_timestamp>2001-12-31T12:00:00</os:control_timestamp>
<os:message_id>000000000000000000000000000000000</os:message_id>
<os:message_type>Contract Enquiry Request</os:message_type>
<os:message_version>ce/v2.2/NameContractRequest</os:message_version>
<os:expected_response_type>synchronous</os:expected_response_type>
<os:initiator_id>initiator_id</os:initiator_id>
<os:initiator_orchestration_id>initiator_orchestration_id</os:initiator_orchestration_id>
<os:KeyInfo>
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=OSIS Customer CA, O=Origo Secure Internet Services Ltd., CN=OSIS Customer CA</ds:X509IssuerName>
<ds:X509SerialNumber>111111111111111111111111111111111111</ds:X509SerialNumber>
</ds:X509IssuerSerial>
<ds:X509SubjectName>C=GB, O=FirmID3400010000023NR11QQ, OU=CPS - www.unipass.co.uk/cps, OU=Warning/Terms of Use - www.unipass.co.uk/tou, OU=EmployeeID10101101010101, OU=TPSP2, OU=BPNR1 1QQ, CN=Testt Orgg/emailAddress=Fname.Lname#aviva.co.uk</ds:X509SubjectName>
</ds:X509Data>
</os:KeyInfo>
<os:responder_id>os:responder_id</os:responder_id>
</ce:m_control>
<ce:m_content>
<ce:b_control>
<ce:contract_enquiry_reference>TestRequest</ce:contract_enquiry_reference>
</ce:b_control>
<ce:intermediary>
<ce:FirmFSARef id="idvalue14">456123</ce:FirmFSARef>
</ce:intermediary>
<ce:request_scope>
<ce:contract_details_required_ind>No</ce:contract_details_required_ind>
<ce:valuation_currency>GBP</ce:valuation_currency>
<ce:fund_code_type_required>SEDOL</ce:fund_code_type_required>
<ce:valuation_request ce:type="Current" />
</ce:request_scope>
<ce:contract>
<ce:contract_reference_number>TL12345678</ce:contract_reference_number>
</ce:contract>
</ce:m_content>
</ce:message>
</soapenv:Body>
</soapenv:Envelope>
################################################ c# code ##################
Guid CEGuid = Guid.NewGuid();
string GuidString = CEGuid.ToString();
string CEVersion = "";
string URL = "";
string ResponderId = "";
string ContractDetailsRequired = "Yes";
using (XmlTextWriter xmlRequestWriter = new XmlTextWriter(#"C:/Unipass/PensionRequest.xml", Encoding.UTF8))
{
xmlRequestWriter.WriteStartDocument();
xmlRequestWriter.WriteComment("This file is generated by the program.");
xmlRequestWriter.WriteStartElement("soapenv:Envelope");
xmlRequestWriter.WriteAttributeString("xmlns:soapenv", null, "http://schemas.xmlsoap.org/soap/envelope/");
xmlRequestWriter.WriteAttributeString("xmlns:ce", null, "http://www.WhateveryDomain.com/schema/ce/v2.2/WhateverRequest");
xmlRequestWriter.WriteAttributeString("xmlns:os", null, "http://www.WhateveryDomain.com/schema/soap/v1");
xmlRequestWriter.WriteAttributeString("xmlns:ds", null, "http://www.w3.org/2000/09/xmldsig#");
xmlRequestWriter.WriteStartElement("soapenv:Header");
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteStartElement("ce:message");
xmlRequestWriter.WriteStartElement("ce:m_control");
xmlRequestWriter.WriteElementString("os:control_timestamp", DateTime.Now.ToString("s"));
xmlRequestWriter.WriteElementString("os:message_id", GuidString);
xmlRequestWriter.WriteElementString("os:message_type", "Contract Enquiry Request");
xmlRequestWriter.WriteElementString("os:message_version", "ce/v2.2/WhateverRequest");
xmlRequestWriter.WriteElementString("os:expected_response_type", "synchronous");
xmlRequestWriter.WriteElementString("os:initiator_id", "initiator_id");
xmlRequestWriter.WriteElementString("os:initiator_orchestration_id", "initiator_orchestration_id");
xmlRequestWriter.WriteStartElement("os:KeyInfo");
xmlRequestWriter.WriteStartElement("ds:X509Data");
xmlRequestWriter.WriteStartElement("ds:X509IssuerSerial");
xmlRequestWriter.WriteElementString("ds:X509IssuerName", "CN=OSIS Customer CA, O=Origo Secure Internet Services Ltd., CN=OSIS Customer CA");
xmlRequestWriter.WriteElementString("ds:X509SerialNumber", "111111111111111111111111111111111");
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteElementString("ds:X509SubjectName", "C=GB, O=FirmID3400010000023NR11QQ, OU=CPS - www.unipass.co.uk/cps, OU=Warning/Terms of Use - www.unipass.co.uk/tou, OU=EmployeeID01200012000003, OU=TPSP2, OU=BPNR1 1QQ, CN=Testt Orgg/emailAddress=fname.lastname#aviva.co.uk");
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteElementString("os:responder_id", "os:responder_id");
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteStartElement("ce:m_content");
xmlRequestWriter.WriteStartElement("ce:b_control");
xmlRequestWriter.WriteElementString("ce:contract_enquiry_reference", "TestRequest");
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteStartElement("ce:intermediary");
xmlRequestWriter.WriteStartElement("ce:FirmFSARef");
xmlRequestWriter.WriteElementString("id", "456123");//="idvalue14">
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteStartElement("ce:request_scope");
xmlRequestWriter.WriteElementString("ce:contract_details_required_ind", "Yes");
xmlRequestWriter.WriteElementString("ce:valuation_currency", "GBP");
xmlRequestWriter.WriteElementString("ce:fund_code_type_required", "SEDOL");
xmlRequestWriter.WriteStartElement("ce:valuation_request");
xmlRequestWriter.WriteElementString("ce:type", "Current");//"ce:type","Current"
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteStartElement("ce:contract");
xmlRequestWriter.WriteElementString("ce:contract_reference_number", "PP12345678");
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndDocument();
xmlRequestWriter.Flush();
Please could you tell me how to send Request and receive response from WCF service using above code.
Even though I have added wsdl reference but I don't know how to pass XmlElement[] in the line below.
serviceReference.getDetail(ref XmlElement[] Any);
Here is a method in Reference class.
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace="http://www.domainname.com/whatever/webname/schema/ce/v2.2/NameContractServic" +
"e", ConfigurationName="TestingPension.NameContractServiceDetailType")]
public interface NameContractServiceDetailType {
// CODEGEN: Generating message contract since the wrapper namespace (http://www.domainname.com/schema/ce/v2.2/NameContractRequest) of message getDetailRequest does not match the default value (http://www.domainname.com/name/name/schema/ce/v2.2/NameContractService)
[System.ServiceModel.OperationContractAttribute(Action="http://www.origostandards.com/schema/ce/v2.2/CEPensionSingleContract#getDetail", ReplyAction="*")]
[System.ServiceModel.FaultContractAttribute(typeof(SoapReqResWebApplication.TestingPension.Error[]), Action="http://www.domainname.com/schema/ce/v2.2/NameContract#getDetail", Name="errors", Namespace="http://www.domainname.com/schema/tech/v1.0/SOAPFaultDetail")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
SoapReqResWebApplication.TestingPension.getDetailResponse getDetail(SoapReqResWebApplication.TestingPension.getDetailRequest request);
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
[System.ServiceModel.MessageContractAttribute(WrapperName="message", WrapperNamespace="http://www.domainname.com/schema/ce/v2.2/NameContractRequest", IsWrapped=true)]
public partial class getDetailRequest {
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="", Order=0)]
[System.Xml.Serialization.XmlAnyElementAttribute()]
public System.Xml.XmlElement[] Any;
public getDetailRequest() {
}
public getDetailRequest(System.Xml.XmlElement[] Any) {
this.Any = Any;
}
}
#CodeCaster
I tried your code as:
var documentToSend = new XmlDocument();
//// TODO: add all elements you like
using (XmlTextWriter xmlRequestWriter = new XmlTextWriter(#"C:/Unipass/Request.xml", Encoding.UTF8))
{
xmlRequestWriter.WriteStartDocument();
xmlRequestWriter.WriteStartElement("ce:message");
xmlRequestWriter.WriteEndElement();
xmlRequestWriter.WriteEndDocument();
xmlRequestWriter.Flush();
documentToSend.Save(xmlRequestWriter);
//// Create an array with the root message node as only element.
var xmlToSend = new XmlElement[] { documentToSend.DocumentElement }; //xmlToSend = null
}
Might be I am doing completely wrong, please can you tell me why xmlToSend is null.

It seems that you are writing code by hand to serialise messages into SOAP/XML. The SOAP request example is an actual instance document rather than WSDL, yes? This is one of the things that WCF does for you - you should not need to generate your own SOAP messages.
In WCF, you focus on the object types you want to transmit and the operations you want to support. Then WCF does all the heavy lifting of SOAP serialisation for you. That is the main point of WCF really.
It's quite hard to decipher from your sample SOAP message what exactly you are trying to do, so I would recommend looking at one of the simple WCF samples, say from here
http://msdn.microsoft.com/en-us/library/vstudio/ms751514(v=vs.90).aspx
Get a simple one up and running using wsHttpBinding or basicHttpBinding (these are SOAP bindings in WCF). Then capture the HTTP request using Fiddler or similar and see what WCF has done for you in terms of the SOAP envelope generation.
You should then be able to translate that to your own case.

It seems like WCF's proxy generator cannot create a class from the provided WSDL/XSD, given the generated method signature is getDetailRequest(System.Xml.XmlElement[] Any). What does a tool like SoapUI say about the WSDL?
A way to solve this is to indeed manually generate the message, which is just the <ce:message>..</ce:message> block. WCF will wrap it in a proper SOAP envelope when you call the service method.
You'll have to play around a bit with the XmlDocument class, but I think something like this will do it:
// Create an XmlDocument and fill it
var documentToSend = new XmlDocument();
// TODO: add all elements you like
// Create an array with the root message node as only element.
var xmlToSend = new XmlElement[] { documentToSend.DocumentElement };
// Call the service.
var response = serviceReference.getDetail(xmlToSend);
Another way would be to manually recreate the request class in C# and populate and serialize an instance of it to XML when sending a request. The ultimate way would be to fix the XML so Add Service Reference (SvcUtil) can generate classes from the service's metadata.
As for your edit, you're not writing the XML elements to the documentToSend, but you write them to your file.

Related

Convert class attributes to UpperCamelCase

I have a class in TypeScript that has attributes written in camelCase.
The instance of that class needs to be used in an http request body that deals with a webservice.
The issues is, the WebService's Backend is written in C#, and it's trying to access the attributes using UpperCamelCase notation.
How can I convert the attributes of a class from camelCase to UpperCamelCase when sending it in an http request ?
Example:
class Test1:
httpClient: CustomHttpClient;
public attributeOne
public attributeTwo
constructor(att1,att2):{
this.attributeOne = att1;
this.attributeTwo = att2;
this.httpClient = new CustomHttpClient();
}
sendRequest(){
const test = new Test1();
this.httpClient.post(url, test , null);
}
The WebService will try to access the body likeWise:
test.AttributeOne
test.AttributeTwo;
Therefore causing an error since no such attributes exist.
I don't want to change my attributes in TS to UpperCamelCase for linting reasons, etc..
How do I achieve this?
Try using HttpParams to set the request body attribute names to whatever you want. Example:
const params = new HttpParams()
.set('para1', "value1")
.set('para2',"value2");
const body=JSON.stringify(person);
return this.http.post<Person>(this.baseURL + 'people', body,{'headers':headers, 'params': params})
Taken from this https://www.tektutorialshub.com/angular/angular-http-post-example/
I managed to do it like wise:
I installed an npm package called 'camelcase-keys' ( npm install camelcase-keys)
I pass my object as a parameter like this:
const body = camelcaseKeys(JSON.parse(JSON.stringify(companyDetails)), { deep: true, pascalCase: true });

C# WCF - Creating custom Message content

I have been inspecting messages sent in a WCF-based system, using the available IClientMessageInspector (and IDispatchMessageInspector).
Currently I'm trying to manually add XML to the message, I cannot manage to get it working.
Situation:
Incoming message has a body like
<s:Body>
<Type xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
...
</Type>
</s:Body>
I want to replace the entire body with custom content, manually structured in a string. I.e., I have a correct XML body in a string, which I want to place within the body of the message.
Is this even possible?
Edit:
To further clarify the question: Can I somehow access the "raw text" of the message, and edit it?
Edit2: I.e. I want to keep the original header and all from the incoming message, but want to replace everything between
<body> </body>
with my custom content which currently resides in a string.
You could use approach similar to one in this blog post https://blogs.msdn.microsoft.com/kaevans/2008/01/08/modify-message-content-with-wcf/
In short you add EndpointBehavior in which you add custom MessageInspector:
Service1Client proxy = new Service1Client();
proxy.Endpoint.Behaviors.Add(new MyBehavior());
public class MyBehavior : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
MyInspector inspector = new MyInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
}
public class MyInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
var xml = "XML of Body goes here";
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
Message replacedMessage = Message.CreateMessage(reply.Version, null, xdr);
replacedMessage.Headers.CopyHeadersFrom(reply.Headers);
replacedMessage.Properties.CopyProperties(reply.Properties);
reply = replacedMessage;
}
}
EDIT: added MemoryStream initiated with data from a string value.

Need help interfacing with the National Rail Enquiries (UK) API (LDBWS)

I've registered for a token to access the National Rail (UK), Live Departure Boards API, but I can't seem to get it to work. Can anybody point out what I'm doing wrong?
I used WSDL.EXE to build a C# SOAP Proxy class, as specified here: https://realtime.nationalrail.co.uk/ldbws/
The generated .cs proxy file is 1318 lines long, but it looks correct.
My code is as follows: (LDBServiceSoap is the generated proxy class)
static void Main(string[] args)
{
LDBServiceSoap ldb = new LDBServiceSoap();
ldb.AccessTokenValue = new AccessToken() {
TokenValue = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
};
StationBoard sb = ldb.GetDepartureBoard(4, "WAT", "VIC", FilterType.from, 0, 120);
}
The Url is set inside the proxy class, so I can't think of anything else I need to do.
But when the debugger gets up to the GetDepartureBoard method, it throws a WebException "The request failed with HTTP status 401: Unauthorized."
Does anybody know what I'm missing / doing wrong here?
You're awesome!
If you obtained your access token through the OpenLDWS signup then your token will only work on the https://realtime.nationalrail.co.uk/OpenLDBWS/ endpoint that could be your problem
Not sure how you've generated your sources but I've managed to get a working version using this Web service client given WSDL to generate the sources from the WSDL:
wsimport -extension -keep -XadditionalHeaders https://lite.realtime.nationalrail.co.uk/OpenLDBWS/wsdl.aspx?ver=2016-02-16
Then I call the method once the sources are on the classpath with the following:
AccessToken token = new AccessToken(); // This class is generated from the WSDL
token.setTokenValue("yourTokenHere");
Ldb ldb = new Ldb();
LDBServiceSoap api = ldb.getLDBServiceSoap();
GetBoardRequestParams reqParams = new GetBoardRequestParams();
reqParams.setCrs("STA"); // Station Code
reqParams.setNumRows(10);
reqParams.setTimeOffset(-120);
StationBoardResponseType departures = api.getDepartureBoard(reqParams, token);
Though the answer from Rob Evans did not completely provide the solution, it did show me the right direction. I had to implement this in C# and below code worked for me.
AccessToken token = new AccessToken();
token.TokenValue = "xxx-xxx-xxx";
ldb client = new ldb();
client.AccessTokenValue = token;
StationBoard sb = client.GetDepartureBoard(10, "ABW", "", FilterType.to, 0, 120);

How to get service reference to generate correctly with message contracts based on 3rd party WSDL, or force no message contracts in WF Service project

I have a problem that given 3rd party WSDL I am able from a Console App to easily create a service proxy that works, but from a WF4 WF service I am not. The generated proxy in the latter case is clearly buggy, involving specifically 2 problems:
a) Message contracts always generated when not requested or needed
b) Incorrect response messages and xml wrapper names used, resulting in null response objects and failed deserialization
The problem I am facing is in the actual generation of the Reference.cs class on the basis of 3rd party WSDL. In the WSDL there are many operations, and in order of appearance 2 of them are as so:
<operation name="pu013">
<documentation>
<description>Check-response service</description>
<help>The service handles (cut out)</help>
</documentation>
<input message="tns:pu013Request" />
<output message="tns:SimpleResponse" />
</operation>
...
<operation name="mi102">
<documentation>
<description>Instruction insert to Matching System</description>
<help>This service (cut out)</help>
</documentation>
<input message="tns:mi102Request" />
<output message="tns:SimpleResponse" />
</operation>
What this results in in the Reference.cs is the following C#:
WorkflowService1.PSE.pu013Response pu013(WorkflowService1.PSE.pu013Request request);
...
WorkflowService1.PSE.pu013Response mi102(WorkflowService1.PSE.mi102Request request);
Note that for some reason the mi102 operation is generated with the INCORRECT response message of pu013Response, which is declared as this:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.MessageContractAttribute(WrapperName="pu013Response", WrapperNamespace="http://pse/", IsWrapped=true)]
public partial class pu013Response {
Note the WrapperName prevents the XML serializer from recognising the response, which is mi102Response, so for all operations that are not pu013 I always get a NULL response.
Also, this does NOT occur if I add a reference from a console application. This does not generate Message contracts, and in this case, invocation and response work.
What is different? Is svcutil being invoke behind the scenes? If so, what is different about the parameters used? Can svcutil be used to generate the xamlx activities too, so that I might find a command line workaround?
This looks like a VS / Add Service Reference bug. The alternative is to manually correct many operations in the Reference.cs.
Ideally, I am looking for a way to easily, automatically, run svcutil or Add Service Reference so that the Reference class is correct and the xamlx activities generated. A nice to have is an explanation of why there is a difference, and behind the scenes what is happening.
UPDATE:
Message contracts generated in the console app result in the same problem - incorrect Response declarations. The problem goes away if parameters are used instead of messages, which are not available from a WF service app.
I am far from an authority on these issues, and while this response below might not be an exact fit to your problem, my recent experience of making a proxyless connection to a service might offer some insight to you or the next person with a similar issue.
I would start by seeing if you can hand roll the SOAP request using fiddler, and see if you are able to create the correct message and send that along. Since you describe the automation tools as being buggy (or perhaps there is a config issue you're not getting just so). Either way, having a clear understanding of the shape of the contract, and being able to perform a reliable test in fiddler may offer clarity.
You don't necessarily need to rely on using a proxy. You can create your own soap message and send it along one of two ways. The first is using the ChannelFactory.
Create your message body (if required, message class can work w/out one)
Create your message
Send your message via the ChannelFactory
For step 1 you can construct your message by making a simple POCO to mirror what is expected in your contract. You should be able to derive what that class is via the WSDL.
Let's say the service is something like this:
[ServiceContract(Namespace = "http://Foo.bar.car")]
public interface IPolicyService
{
[OperationContract]
PolicyResponse GetPolicyData(PolicyRequest request);
}
public class PolicyData : IPolicyService
{
public PolicyResponse GetPolicyData(PolicyRequest request)
{
var polNbr = request.REQ_POL_NBR;
return GetMyData(polNbr);
}
}
You would need a class something like this:
[DataContract(Namespace = "http://Foo.bar.car")]
public class GetPolicyData
{
[DataMember]
public request request { get; set; }
}
[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy")]
public class request
{
///<summary>
/// Define request parameter for SOAP API to retrieve selective Policy level data
/// </summary>
[DataMember]
public string REQ_POL_NBR { get; set; }
}
and then you would call it like this:
private static Message SendMessage(string id)
{
var body = new GetPolicyData {request = new request{ REQ_POL_NBR = id }};
var message = Message.CreateMessage(MessageVersion.Soap11, "http://Foo.bar.car/IPolicyService/GetPolicyData", body);
// these headers would probably not be required, but added for completeness
message.Headers.Add(MessageHeader.CreateHeader("Accept-Header", string.Empty, "application/xml+"));
message.Headers.Add(MessageHeader.CreateHeader("Content-Type", string.Empty, "text/xml"));
message.Headers.Add(MessageHeader.CreateHeader("FromSender", string.Empty, "DispatchMessage"));
message.Headers.To = new System.Uri(#"http://localhost:5050/PolicyService.svc");
var binding = new BasicHttpBinding(BasicHttpSecurityMode.None)
{
MessageEncoding = WSMessageEncoding.Text,
MaxReceivedMessageSize = int.MaxValue,
SendTimeout = new TimeSpan(1, 0, 0),
ReaderQuotas = { MaxStringContentLength = int.MaxValue, MaxArrayLength = int.MaxValue, MaxDepth = int.MaxValue }
};
message.Properties.Add("Content-Type", "text/xml; charset=utf-8");
message.Properties.Remove("Accept-Encoding");
message.Properties.Add("Accept-Header", "application/xml+");
var cf = new ChannelFactory<IRequestChannel>(binding, new EndpointAddress(new Uri("http://localhost:5050/PolicyService.svc")));
cf.Open();
var channel = cf.CreateChannel();
channel.Open();
var result = channel.Request(message);
channel.Close();
cf.Close();
return result;
}
What you receive back will be a Message, which you will need to deserialize, and there are a few OOTB ways of doing this, (Message.GetReaderAtBodyContents, Message.GetBody) in keeping w/the hand-rolled theme:
/// <summary>
/// Class MessageTransform.
/// </summary>
public static class MessageTransform
{
/// <summary>
/// Gets the envelope.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>XDocument.</returns>
public static XDocument GetEnvelope(Message message)
{
using (var memoryStream = new MemoryStream())
{
var messageBuffer = message.CreateBufferedCopy(int.MaxValue);
var xPathNavigator = messageBuffer.CreateNavigator();
var xmlWriter = XmlWriter.Create(memoryStream);
xPathNavigator.WriteSubtree(xmlWriter);
xmlWriter.Flush();
xmlWriter.Close();
memoryStream.Position = 0;
var xdoc = XDocument.Load(XmlReader.Create(memoryStream));
return xdoc;
}
}
/// <summary>
/// Gets the header.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>XNode.</returns>
public static XNode GetHeader(Message message)
{
var xdoc = GetEnvelope(message);
var strElms = xdoc.DescendantNodes();
var header = strElms.ElementAt(1);
return header;
}
/// <summary>
/// Gets the body.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="localName">Name of the local.</param>
/// <param name="namespaceName">Name of the namespace.</param>
/// <returns>IEnumerable<XElement>.</returns>
public static IEnumerable<XElement> GetBody(Message message, string localName, string namespaceName)
{
var xdoc = GetEnvelope(message);
var elements = xdoc.Descendants(XName.Get(localName, namespaceName));
return elements;
}
}
OR you could build your soap envelope by hand and use WebClient:
using System.Net;
using System.Xml.Linq;
public static class ClientHelper
{
public static string Post(string targetUrl, string action, string method, string key, string value)
{
var request = BuildEnvelope(method, key, value);
using (var webClient = new WebClient())
{
webClient.Headers.Add("Accept-Header", "application/xml+");
webClient.Headers.Add("Content-Type", "text/xml; charset=utf-8");
webClient.Headers.Add("SOAPAction", action);
var result = webClient.UploadString(targetUrl, "POST", request);
return result;
}
}
public static string BuildEnvelope(string method, string key, string value)
{
XNamespace s = "http://schemas.xmlsoap.org/soap/envelope/";
XNamespace d = "d4p1";
XNamespace tempUri = "http://tempuri.org/";
XNamespace ns = "http://Foo.bar.car";
XNamespace requestUri = "http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy";
var xDoc = new XDocument(
new XElement(
s + "Envelope",
new XAttribute(XNamespace.Xmlns + "s", s),
new XElement(
s + "Body",
new XElement(
ns + method,
new XElement(requestUri + "request",
new XElement(tempUri + key, value))
)
)
)
);
// hack - finish XDoc construction later
return xDoc.ToString().Replace("request xmlns=", "request xmlns:d4p1=").Replace(key, "d4p1:" + key);
}
which is called with:
return ClientHelper.Post("http://localhost:5050/PolicyService.svc", "http://Foo.bar.car/IPolicyService/GetPolicyData", "GetPolicyData", "REQ_POL_NBR", id);
Testing it in Fiddler would look something like this:
Post action: http://localhost:5050/PolicyService.svc
Header:
User-Agent: Fiddler
SOAPAction: http://Foo.bar.car/IPolicyService/GetPolicyData
Content-type: text/xml
Host: localhost:5050
Content-Length: 381
Body:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetPolicyData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://Foo.bar.car">
<request xmlns:d4p1="http://schemas.datacontract.org/2004/07/Foo.bar.car.Model.Policy">
<d4p1:REQ_POL_NBR>1</d4p1:REQ_POL_NBR>
</request>
</GetPolicyData>
</s:Body>
</s:Envelope>
Again, this answer isn't trying to resolve how to invoke svcUtil differently, but to avoid calling it altogether, so I hope the edit gods don't ding me for that ;-)
My code above has been inspired by better developers than I, but I hope it helps.
http://blogs.msdn.com/b/stcheng/archive/2009/02/21/wcf-how-to-inspect-and-modify-wcf-message-via-custom-messageinspector.aspx
I would suggest you to generated wsdl proxy using command line utility and add generated proxy file in your project. It will work from every project and you can find the required configurations from output.config that will generate from command line utility.
If you require the wsdl command and options then I can provide you.

Duplicate security elements when building custom soap request

I'm trying to send a soap request to a WCF service. I am building the soap request using the System.ServiceModel.Channels.Message.CreateMessage() method.
I haven't gotten super deep into building the body, but here is what I have...
Message msg = Message.CreateMessage( MessageVersion.Soap11WSAddressing10, "MethodName" );
msg.Headers.MessageId = new UniqueId( Guid.NewGuid().ToString() );
msg.Headers.Add( Message.CreateHeader( "Security", "",
new Security()
{
TimeStamp = new TimeStampType() {
Created = DateTime.Now,
Expires = Created.AddDays( 1 )
},
UsernameToken = new UsernameToken() {
Username = "stackoverflow",
Password = new Password() {
Type = "hashed",
Value = "Password"
}
}
}, new SecuritySerializer() ) ) ); // The serializer inherits XmlObjectSerializer. Internally it is using the XmlSerializer class.
string s = msg.ToString();
When I run this, I get the following output. I'm using the Visual Studio XML Visualizer btw.
<s:Envelope>
<s:Header>
<Action>MethodName</Action>
<MessageID>GUIDVALUE</MessageID>
<Security>
<Security>
.....
</Security>
</Security>
</s:Header>
<s:Body />
</s:Envelope>
My question is, how can I remove one of the Security elements? I'm supposed to have one, but I have two since I first passed in the element name to the Message.CreateHeader() method and the second one was generated from the serializer.
Thanks in advance
I fixed this by making the Security class inherit from the MessageHeader abstract class. I then overrided the OnWriteHeaderContents method and serialized the properties there. Also I took out MessageHeader.CreateHeader() since the Security object is now a header object.

Categories

Resources