C# - xmldoc.selectSingleNode(xpath, nsmanager) returns null - c#

I have a Soap envelope which is being used as request packet for a webservice.
I am setting the nodes values dynamically using the values from a grid
xDoc.SelectSingleNode(xPath, oManager).InnerXml = r.Cells[2].Value.ToString();
My xml packet is :
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body>
<CardActivation xmlns="www.testclient.com">
<requestData xmlns:d4p1="http://schemas.testdata.org/2004/07/ClientServices.DTO" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<LoginUser xmlns="http://schemas.testdata.org/2004/07/ClientServices">testuser</LoginUser>
<UserPassword xmlns="http://schemas.testdata.org/2004/07/ClientServices">Testped</UserPassword>
<IPAddress xmlns="http://schemas.testdata.org/2004/07/ClientServices">10.211.1.22</IPAddress>
<UniqueIDFlag xmlns="http://schemas.testdata.org/2004/07/ClientServices">0</UniqueIDFlag>
<UniqueID xmlns="http://schemas.testdata.org/2004/07/ClientServices">1234567890123457502</UniqueID>
<Source xmlns="http://schemas.testdata.org/2004/07/ClientServices">WS</Source>
<APIVersion xmlns="http://schemas.testdata.org/2004/07/ClientServices">1.2</APIVersion>
<ApplicationVersion xmlns="http://schemas.testdata.org/2004/07/ClientServices">1</ApplicationVersion>
<CallerID xmlns="http://schemas.testdata.org/2004/07/ClientServices">221221</CallerID>
<CalledID xmlns="http://schemas.testdata.org/2004/07/ClientServices">3333</CalledID>
<SessionID xmlns="http://schemas.testdata.org/2004/07/ClientServices">221111</SessionID>
<ANI i:nil="true" xmlns="http://schemas.testdata.org/2004/07/ClientServices" />
<DNS i:nil="true" xmlns="http://schemas.testdata.org/2004/07/ClientServices" />
<Language i:nil="true" xmlns="http://schemas.testdata.org/2004/07/ClientServices" />
<RequestDate xmlns="http://schemas.testdata.org/2004/07/ClientServices">2014-10-08T00:00:00</RequestDate>
<d4p1:CardNumber i:nil="true" />
<d4p1:ProxyNumber>1123</d4p1:ProxyNumber>
<d4p1:CardExpiryDate>2105</d4p1:CardExpiryDate>
</requestData>
</CardActivation> </s:Body> </s:Envelope>
When I am trying to set value to UserPassword node, selectSingleNode is returning null. My namespacemanger code is as follows.
public static XmlNamespaceManager getAllNamespaces(XmlDocument xDoc)
{
XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
IDictionary<string, string> localNamespaces = null;
XPathNavigator xNav = xDoc.CreateNavigator();
while (xNav.MoveToFollowing(XPathNodeType.Element))
{
localNamespaces = xNav.GetNamespacesInScope(XmlNamespaceScope.Local);
// localNamespaces = xNav.GetNamespacesInScope(XmlNamespaceScope.All);
foreach (var localNamespace in localNamespaces)
{
string prefix = localNamespace.Key;
if (string.IsNullOrEmpty(prefix))
prefix = "DEFAULT";
result.AddNamespace(prefix, localNamespace.Value);
}
}
return result;
}
Do I need to explicitly change my xpath to add prefixes to my default namespace? Is there any approach to do this dynamically -adding prefixes to each node using the namespacemanager? Please help..

I found a workaround for this issue without doing any manipulations in the xpath.
Here goes my solution.
Replace the default namespace 'xmlns= ' with 'xns='(or any other token
which wont be a possible conflict with any existing prefixes).After
entire processing is done with xDoc, before sending to service, update
it back to its previous state for avoiding any namespace related
issues from the service.
if (xDoc.InnerXml.Contains("xmlns=\""))
{
xDoc.InnerXml = xDoc.InnerXml.Replace("xmlns=\"", "xns=\"");
}

Related

How to add the same XML tags multiple times, with different content value in (c#) .Net Core

I am creating and API in .Net core using webservice .wsdl file, I have hardcoded the xml like below:
XDocument xDocument = XDocument.Parse(
"<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'><soapenv:Header/>\r\n<soapenv:Body>\r\n<MyServiceRequest>\r\n<ITEMSLIST>\r\n<ITEMS>\r\n<ID>$" +request.ID+"</ID>\r\n<NAME>" + request.NAME+ "</NAME>\r\n</ITEMS>\r\n</ITEMSLIST>\r\n</acc:MyServiceRequest>\r\n</soapenv:Body>\r\n</soapenv:Envelope>");
var xmlRequestBody = xDocument.ToString();
Values are adding in array from this model:
Items[] request
public class Items
{
public string ID { get; set; }
public string NAME { get; set; }
}
I am adding values dynamically, for single value it is working fine, but when adding multiple values it is not working.
XML file is:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header />
<soapenv:Body>
<acc:MyServiceRequest>
<ITEMSLIST>
<ITEMS>
<ID>06285883</ID>
<NAME>John</NAME>
</ITEMS>
</ITEMSLIST>
</acc:MyServiceRequest>
</soapenv:Body>
</soapenv:Envelope>
for multiple values the xml should look like this, before sending the request. the values should be filled dynamically from the request model.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header />
<soapenv:Body>
<acc:MyServiceRequest>
<ITEMSLIST>
<ITEMS>
<ID>06285883</ID>
<NAME>John</NAME>
</ITEMS>
<ITEMS>
<ID>06285231</ID>
<NAME>Sara</NAME>
</ITEMS>
</ITEMSLIST>
</acc:MyServiceRequest>
</soapenv:Body>
</soapenv:Envelope>
Can anyone guide How I can add multiple values in the same xml?
* I'm trying to give the main idea
I just removed all those \r\n.
Found the ITEMSLIST element out side of the loop.
Iterat all items and for each one, make an XElement object and add it to itemsList element.
In the end, the result is in xDocument object.
private static XElement xDocument = XElement.Parse(
#"<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'>
<soapenv:Header/>
<soapenv:Body>
<MyServiceRequest>
<ITEMSLIST>
</ITEMSLIST>
</MyServiceRequest>
</soapenv:Body>
</soapenv:Envelope>");
public void Test()
{
Items[] items = new Items[]
{
new Items { ID = "06285883", NAME = "John" },
new Items { ID = "06285231", NAME = "Sara" }
};
var itemsList = xDocument.Descendants("ITEMSLIST").Single();
foreach (var item in items)
{
XElement element = new XElement("ITEMS");
element.Add(new XElement("ID", item.ID));
element.Add(new XElement("NAME", item.NAME));
itemsList.Add(element);
}
Console.WriteLine(xDocument);
}

How can I create custom XML namespace attributes when consuming a legacy SOAP service?

I have a legacy Tibco SOAP service that I need to get some data from. Unfortunately this service is very particular about the XML namespace attributes on the request message. I have also run into this sort of problem when consuming services from PeopleSoft (https://en.wikipedia.org/wiki/PeopleCode).
I got the .wsdl from the service and created a service reference.
Out of the box, the XML request message that .Net produces is:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<getLocation xmlns="http://customNamespaceHere">
<context>
<source>SysAdmin</source>
</context>
<Address>
<address1>123 Main St</address1>
<city>New York</city>
<state>NY</state>
<country>US</country>
</Address>
</getLocation>
</s:Body>
</s:Envelope>
What actually works is (I figured this out using SoapUI):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<getLocation xmlns="http://customNamespaceHere">
<context>
<source>SysAdmin</source>
</context>
<Address>
<address1>123 Main St</address1>
<city>New York</city>
<state>NY</state>
<country>US</country>
</Address>
</getLocation>
</s:Body>
</s:Envelope>
Notice the absence of the xsi and xsd prefixes within the body tag.
Q: How can I get .Net to send the correct XML short of hand rolling the XML document and manually sending it to the service?
A: I was able to modify the XML request before sending it out by implementing IClientMessageInspector.
First I had to extend WCF by implementing IClientMessageInspector. This allowed me to gain access to the request object and modify the XML request message. These classes don't need to be in any particular location within you solution (as far as I know).
public class ExtendedClientMessageInspector : IClientMessageInspector
{
// Here we can alter the xml request body before it gets sent out.
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return TibcoService.ModifyGetLocationRequest(ref request);
} // end
public void AfterReceiveReply(ref Message reply, object correlationState)
{
return;
} //end
} // end class
public class ExtendedEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
} // end
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new ExtendedClientMessageInspector());
} // end
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
return;
} // end
public void Validate(ServiceEndpoint endpoint)
{
return;
} // end
} // end class
public class EndpointBehaviorExtensionElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ExtendedEndpointBehavior();
} // end
public override Type BehaviorType
{
get
{
return typeof(ExtendedEndpointBehavior);
}
} // end
} // end class
I did put the method that specifically deals with the XML in this service in the same class as the service:
public static Message ModifyGetLocationRequest(ref Message request)
{
// Initialize objects
var xmlDocument = new XmlDocument();
var memoryStream = new MemoryStream();
var xmlWriter = XmlWriter.Create(memoryStream);
var xmlAttribute = xmlDocument.CreateAttribute("xmlns", "api", "http://www.w3.org/2000/xmlns/");
xmlAttribute.Value = "http://customNamespaceHere";
// Write the xml request message into the memory stream
request.WriteMessage(xmlWriter);
// Clear the xmlWriter
xmlWriter.Flush();
// Place the pointer in the memoryStream to the beginning
memoryStream.Position = 0;
// Load the memory stream into the xmlDocument
xmlDocument.Load(memoryStream);
// Remove the attributes from the second node down form the top
xmlDocument.ChildNodes[1].ChildNodes[1].Attributes.RemoveAll();
// Reset the memoryStream object - essentially nulls it out
memoryStream.SetLength(0);
// ReInitialize the xmlWriter
xmlWriter = XmlWriter.Create(memoryStream);
// Write the modified xml request message (xmlDocument) to the memoryStream in the xmlWriter
xmlDocument.WriteTo(xmlWriter);
// Clear the xmlWriter
xmlWriter.Flush();
// Place the pointer in the memoryStream to the beginning
memoryStream.Position = 0;
// Create a new xmlReader with the memoryStream that contains the xmlDocument
var xmlReader = XmlReader.Create(memoryStream);
// Create a new request message with the modified xmlDocument
request = Message.CreateMessage(xmlReader, int.MaxValue, request.Version);
return request;
} // end
To get this all to work when the service is called you will need to modify your web.config or app.config. On your endpoint declaration you will need to specify a behaviorConfiguration element like so:
<client>
<endpoint address="http://1.2.3.4:1234/InventoryBinding"
binding="basicHttpBinding" bindingConfiguration="HttpSoapBinding"
contract="TibcoSvc.InventoryPort" name="InventoryPort"
behaviorConfiguration="clientInspectorsAdded" />
</client>
You will also need to specify the extension and the behavior as well:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="ExtendedEndpointBehavior" type="Sample.Integrations.EndpointBehaviorExtensionElement, Sample.Integrations, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="clientInspectorsAdded">
<ExtendedEndpointBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Note here that the extension name needs to be exactly what is returned by:
var foo = typeof(<PutYourNamespaceHereNoAngleBrackets>.EndpointBehaviorExtensionElement).AssemblyQualifiedName;
Here are a few links that I found helpful:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/01440583-d406-4426-8667-63c6eda431fa/remove-xmlnsxsi-and-xmlnsxsd-from-soap-request-body-tag-aspnet?forum=wcf
https://social.msdn.microsoft.com/Forums/vstudio/en-US/51547537-fdae-4837-9bd1-30e445d378e9/removing-xmlnsxsihttpwwww3org2001xmlschemainstance-and?forum=wcf
http://weblogs.asp.net/paolopia/writing-a-wcf-message-inspector
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector(v=vs.100).aspx

wcf service datacontract last property not added

I'm adding a property to my model with an enum.
public class ChildCareCentreEntryReservation : BasketItem, ILockableBasketItem
{
....
[DataMember]
public ChildCareCentreEntryReservationStatusTypes ChildCareCentreEntryReservationStatusType { get; set; }
}
[DataContract(Namespace = Constants.Namespace)]
public enum ChildCareCentreEntryReservationStatusTypes
{
[EnumMember]
VoorlopigIngeschreven = 0,
[EnumMember]
DefinitiefIngeschreven = 1,
[EnumMember]
Geannuleerd = 2
}
In my form I Create a ChildCareCentreEntryReservation object:
var reservation = new ChildCareCentreEntryReservation();
reservation.ChildCareEntryPeriodId = _entryPeriod.Id;
reservation.ChildCareCentreId = _centre.Id;
reservation.PersonId = _person.Id;
reservation.Comment = txtComment.Text;
reservation.ChildCareCentreEntryReservationStatusType = (ChildCareCentreEntryReservationStatusTypes)cboStatusName.SelectedIndex;
I add the ChildCareCentreEntryReservation object to a list:
var basketItems = new List<BasketItem> { reservation };
Then I send an array to the service by calling the function LockBasketItems:
LockBasketResult lockBasketResult = Service.LockBasketItems(basketItems.ToArray(), Context);
Everything builds and works correctly but my service doesn't receive my last added property.
Fiddler inspectors result (client request to service):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<BasketItems xmlns="http://www./">
<BasketItem xsi:type="ChildCareCentreEntryReservation">
<RuleNamesToIgnore xsi:nil="true"/>
<ChildCareEntryPeriodId>13ccefb3-f1e4-4f64-8fb8-b07cf30d2fca</ChildCareEntryPeriodId>
<ChildCareCentreId>8cc85f37-da5d-46c5-9bb4-d6efa8448176</ChildCareCentreId>
<PersonId>56e341bb-ac05-40dc-a39a-082ae4ff087e</PersonId>
<ChildCareCentrePeriodIds>
<guid xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">61c43967-8781-4d83-a117-963c5734491f</guid>
</ChildCareCentrePeriodIds>
<LockTicket xsi:nil="true"/>
<Comment/>
<PeriodOptions xsi:nil="true"/>
</BasketItem>
</BasketItems>
<Context xmlns="http://www./">
<Language>NL</Language>
<ShopId>00000000-0000-0000-0000-000000000550</ShopId>
<SessionId>de824345-14f3-44c7-99fd-f9a073e9b51b</SessionId>
</Context>
Fiddler inspectors result (service response to client):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<ActivityId CorrelationId="d1daee11-20e2-4e98-8774-9bffe4e2e4f3" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">728e68a9-17ff-44a0-b4ad-f455f403be5e</ActivityId>
</s:Header>
<s:Body>
<LockBasketResult xmlns="http://www./" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<BasketItems>
<BasketItem i:type="ChildCareCentreEntryReservation">
<DivisionId>00000000-0000-0000-0000-000000000000</DivisionId>
<Id>00000000-0000-0000-0000-000000000000</Id>
<Quantity>1</Quantity>
<RuleNamesToIgnore xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>
<UnitPrice>0</UnitPrice>
<ChildCareEntryPeriodId>13ccefb3-f1e4-4f64-8fb8-b07cf30d2fca</ChildCareEntryPeriodId>
<ChildCareCentreId>8cc85f37-da5d-46c5-9bb4-d6efa8448176</ChildCareCentreId>
<PersonId>56e341bb-ac05-40dc-a39a-082ae4ff087e</PersonId>
<ChildCareCentrePeriodIds xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:guid>61c43967-8781-4d83-a117-963c5734491f</a:guid>
</ChildCareCentrePeriodIds>
<LockTicket i:type="ChildCareCentreEntryReservationLockTicket">
<ExpirationTime>2013-10-08T17:27:06</ExpirationTime>
<Id>13a984f8-5d97-4f87-aa52-7523849cc6f5</Id>
</LockTicket>
<Comment/>
<PeriodOptions xmlns:a="http://schemas.datacontract.org/"/>
<ChildCareCentreEntryReservationStatusType>VoorlopigIngeschreven</ChildCareCentreEntryReservationStatusType>
</BasketItem>
</BasketItems>
<IsLocked>true</IsLocked>
<ValidationResult i:nil="true"/>
</LockBasketResult>
In my Reference.cs i'm seeing the property is correctly added.
Any idea what i am missing?
I'm using ASP.NET
Solution
I had to set ChildCareCentreEntryReservationStatusTypeSpecified to true
reservation.ChildCareCentreEntryReservationStatusTypeSpecified = true;
Solved
I had to set ChildCareCentreEntryReservationStatusTypeSpecified to true
reservation.ChildCareCentreEntryReservationStatusTypeSpecified = true;

Specifying Reference URI of #Body while digitally signing SOAP request - using WCF

I am using a WCF client to talk to a non-WCF web service.
This web service requires that the body of the SOAP message is signed, however, I am having trouble generating a valid SOAP request.
I have implemented a ClientMessageInspector which inherits from IClientMessageInspector, where I modify the message in the BeforeSendRequest method to add the XML digital signature. I use the SignedXML class to do this.
I am using the IBM Web Services Validation Tool for WSDL and SOAP to check whether my digital signature verifies.
My problem is that when I specify a full namespaced reference in the Reference URI, the IBM tools that I'm using say that I have a valid signature. From the XML Digital Signature specifications, I should just be able to reference the attribute, without the namespace, however, when I do this, I don't get a valid digital signature.
This is the SOAP request I am currently generating, which my tools say has a valid signature, but the web service doesn't like it:
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<s:Header>
<soapsec:Signature>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="http://schemas.xmlsoap.org/soap/security/2000-12#Body">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>4mt5wluUTu5tpR2d5UemVSLvqTs=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>UZ7HzfE3GxIY9hg...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIEkTCCA3mgAwIBAgIQCu...</X509Certificate>
</X509Data>
<KeyValue>
<RSAKeyValue>
<Modulus>0C3e9HDx5Yq6FLUxIgjJ...</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</soapsec:Signature>
</s:Header>
<s:Body soapsec:id="Body">
.... SOAP Body Here ...
</s:Body>
</s:Envelope>
This is the SOAP request I want to be generating, but my tools say this has an invalid signature, and the web service also tells me the signature is invalid:
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<s:Header>
<soapsec:Signature>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Body">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>4mt5wluUTu5tpR2d5UemVSLvqTs=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>UZ7HzfE3GxIY9hg...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIEkTCCA3mgAwIBAgIQCu...</X509Certificate>
</X509Data>
<KeyValue>
<RSAKeyValue>
<Modulus>0C3e9HDx5Yq6FLUxIgjJ...</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</soapsec:Signature>
</s:Header>
<s:Body soapsec:id="Body">
.... SOAP Body Here ...
</s:Body>
</s:Envelope>
And here is the code I have in BeforeSendRequest to create the signature, and modify the message accordingly:
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(request.ToString());
// Add the required namespaces to the SOAP Envelope element, if I don't do this, the web service I'm calling returns an error
string soapSecNS = "http://schemas.xmlsoap.org/soap/security/2000-12";
string soapEnvNS = "http://www.w3.org/2003/05/soap-envelope";
//Get the header element, so that we can add the digital signature to it
XmlNode headerNode = doc.GetElementsByTagName("Header", soapEnvNS)[0];
// Set the ID attribute on the body element, so that we can reference it later
XmlNode bodyNode = doc.GetElementsByTagName("Body", soapEnvNS)[0];
((XmlElement)bodyNode).RemoveAllAttributes();
((XmlElement)bodyNode).SetAttribute("id", soapSecNS, "Body");
XmlWriterSettings settings2 = new XmlWriterSettings();
settings2.Encoding = new System.Text.UTF8Encoding(false);
// Load the certificate we want to use for signing
SignedXmlWithId signedXml = new SignedXmlWithId(doc);
X509Certificate2 cert = new X509Certificate2("C:\\myCertificate.pfx", "myPassword");
signedXml.SigningKey = cert.PrivateKey;
//Populate the KeyInfo element correctly, with the public cert and public key
Signature sigElement = signedXml.Signature;
KeyInfoX509Data x509Data = new KeyInfoX509Data(cert);
sigElement.KeyInfo.AddClause(x509Data);
RSAKeyValue rsaKeyValue = new RSAKeyValue((RSA)cert.PublicKey.Key);
sigElement.KeyInfo.AddClause(rsaKeyValue);
// Create a reference to be signed, only sign the body of the SOAP request, which we have given an
// ID attribute to, in order to reference it correctly here
Reference reference = new Reference();
reference.Uri = soapSecNS + "#Body";
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
XmlElement soapSignature = doc.CreateElement("Signature", soapSecNS);
soapSignature.Prefix = "soapsec";
soapSignature.AppendChild(xmlDigitalSignature);
headerNode.AppendChild(soapSignature);
// Make sure the byte order mark doesn't get written out
XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas();
Encoding encoderWithoutBOM = new System.Text.UTF8Encoding(false);
System.IO.MemoryStream ms = new System.IO.MemoryStream(encoderWithoutBOM.GetBytes(doc.InnerXml));
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(ms, encoderWithoutBOM, quotas, null);
//Create the new message, that has the digital signature in the header
Message newMessage = Message.CreateMessage(xdr, System.Int32.MaxValue, request.Version);
request = newMessage;
return null;
}
Does anybody know how I can set the Reference URI to #Body, but also have a valid XML Signature?
Since I was trying to generate a signature for a SOAP request, I got around this by subclassing SignedXml, overriding GetIdElement, so that I can return whatever element it is that I'm looking for. In this case, the id element will belong to the namespace http://schemas.xmlsoap.org/soap/security/2000-12
public class SignedXmlWithId : SignedXml
{
public SignedXmlWithId(XmlDocument xml)
: base(xml)
{
}
public SignedXmlWithId(XmlElement xmlElement)
: base(xmlElement)
{
}
public override XmlElement GetIdElement(XmlDocument doc, string id)
{
// check to see if it's a standard ID reference
XmlElement idElem = base.GetIdElement(doc, id);
if (idElem == null)
{
// I've just hardcoded it for the time being, but should be using an XPath expression here, and the id that is passed in
idElem = (XmlElement)doc.GetElementsByTagName("Body", "http://schemas.xmlsoap.org/soap/security/2000-12")[0];
}
return idElem;
}
}
I've also realised that using tools such as IBM Web Services Validation Tool for WSDL and SOAP to validate the digital signature of the SOAP request doesn't work. Instead I'm using the following method to verify signatures:
public static bool verifyDigitalSignatureForString(string msgAsString)
{
XmlDocument verifyDoc = new XmlDocument();
verifyDoc.PreserveWhitespace = true;
verifyDoc.LoadXml(msgAsString);
SignedXmlWithId verifyXml = new SignedXmlWithId(verifyDoc);
// Find the "Signature" node and create a new
// XmlNodeList object.
XmlNodeList nodeList = verifyDoc.GetElementsByTagName("Signature");
// Load the signature node.
verifyXml.LoadXml((XmlElement)nodeList[0]);
if (verifyXml.CheckSignature())
{
Console.WriteLine("Digital signature is valid");
return true;
}
else
{
Console.WriteLine("Digital signature is not valid");
return false;
}
}

WCF set custom header - reading not working

I need to put custom headers into WCF. My Code is as follows:
ServiceReference1.Service2Client ws = new Service2Client();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)ws.InnerChannel))
{
MessageHeaders messageHeadersElement = OperationContext.Current.OutgoingMessageHeaders;
messageHeadersElement.Add(MessageHeader.CreateHeader("Authorization", String.Empty, "string"));
messageHeadersElement.Add(MessageHeader.CreateHeader("username", String.Empty, "user"));
var res = ws.GetUser("123");
}
But when I try to read it in the service, nothing is availabe in the following
public class OAuthAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
int index = OperationContext.Current.IncomingMessageHeaders.FindHeader("username", String.Empty);
string auth = operationContext.IncomingMessageHeaders.GetHeader<string>("username", String.Empty);
var hereIseeIt = operationContext.RequestContext.RequestMessage;
index is -1: not found
auth: is also displaying an exception that the header is not available
hereIseeIt: .ToString() shows a xml where I can see that user is existent, but I see no way to access that information in any of the objects
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<username xmlns="http://Microsoft.WCF.Documentation">user</username>
</s:Header>
<s:Body>
<GetUser xmlns="http://tempuri.org/">
<UserId>123</UserId>
</GetUser>
</s:Body>
</s:Envelope>
But I cannot access them since I find no way to access the s:Header ...
try using:
XPathNavigator XPN = operationContext.RequestContext.RequestMessage.CreateBufferedCopy ().CreateNavigator ();
NOT elegant but it gives you the whole Message accessible through a XPathNavigator which should make it easy to get to any value inside the Message you want..
some links:
http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.requestcontext.aspx
http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.message.aspx
http://msdn.microsoft.com/en-us/library/system.xml.xpath.xpathnavigator.aspx
Here's an easy way to get the inner XML of the username header for your scenario. Even if you already solved your issue a long time ago, I thought it might help somebody else who faces the same issue.
var username = String.Empty;
// using the namespace from you XML sample
var usernameHeaderPosition = OperationContext.Current
.IncomingMessageHeaders
.FindHeader("username", "http://Microsoft.WCF.Documentation");
if (usernameHeaderPosition > -1)
{
username = OperationContext.Current
.IncomingMessageHeaders
.GetReaderAtHeader(usernameHeaderPosition).ReadInnerXml();
}

Categories