I implemented a custom message inspector (via IDispatchMessageInspector) to intercept messages that are received on the server side of a WCF service so I can attempt to deserialize the message and apply some specific business logic. The problem I'm encountering is when I write the MessageBuffer's contents to a new MemoryStream and then try to deserialize, I get an error that says "The data at the root level is invalid. Line 1, position 1." I do know the data being passed in is valid as skipping over the inspector makes everything work fine.
Sample Code:
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
string msg = buffer.CreateMessage().ToString();
var dc = new DataContractSerializer(typeof(Adder));
using (var stream = new MemoryStream())
{
buffer.WriteMessage(stream);
stream.Position = 0;
//deserializing error occurs here
var c = dc.ReadObject(stream);
}
return null;
}
Here is the Adder class/interface:
[DataContract(Name = "adder", Namespace = "http://test.com")]
public class Adder
{
[DataMember(Name = "first")]
public int First { get; set; }
[DataMember(Name = "second")]
public int Second { get; set; }
}
[ServiceContract(Namespace = "http://test.com")]
public interface ITestSvc
{
[OperationContract(Name = "add")]
int Add(Adder adder);
}
Any suggestions or is there a better option for this? My main goal is to read the XML (in a deserialized object) on every WCF request that comes into my service.
The request object contains the WCF message headers as well as the payload. You'll need to strip off the headers and then you should be able to deserialize the message body.
For example, a SOAP message would have:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
</soap:Header>
<soap:Body>
<!-- your payload -->
</soap:Body>
You could use XML navigation to get to the body element and then you'd deserialize that element alone.
EDIT:
Actually just stumbled on this method which I think should do the trick for you:
Message.GetReaderAtBodyContents
I just did this. Just paste all of the following code into your class, and call DeserializedResponse(). You'll need to change MyResponseObject to the name of whatever object you are trying to deserialize. Also, you will need to replace "_requestInspector.Response" with your own response xml string variable. The method GetSoapBodyInnerXml() will strip off the Soap Envelope, and only return your response xml which you wish to deserialize.
private MyResponseObject DeserializedResponse()
{
var rootAttribute = new XmlRootAttribute("MyResponseObject ");
rootAttribute.Namespace = #"http://www.company.com/MyResponseObjectNamespace";
XmlSerializer serializer = new XmlSerializer(typeof(MyResponseObject ), rootAttribute);
string responseSoapBodyInnerXml = GetSoapBodyInnerXml(_requestInspector.Response);
AddXmlDeclaration(ref responseSoapBodyInnerXml);
MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(responseSoapBodyInnerXml));
MyResponseObject resultingResponse = (MyResponseObject )serializer.Deserialize(memStream);
return resultingResponse;
}
private string GetSoapBodyInnerXml(string soapMessage)
{
XDocument xDoc = XDocument.Parse(soapMessage);
XNamespace nsSoap = #"http://schemas.xmlsoap.org/soap/envelope/";
return xDoc.Descendants(nsSoap + CONST_SoapBody).Descendants().First().ToString();
}
private void AddXmlDeclaration(ref string xmlString)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlString);
//Create an XML declaration.
XmlDeclaration xmldecl;
xmldecl = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
//Add the new node to the document.
XmlElement root = doc.DocumentElement;
doc.InsertBefore(xmldecl, root);
//Return updated xmlString with XML Declaration
xmlString = doc.InnerXml;
}
I ended up going the message header route, like Mike Parkhill suggested.
Related
In the WCF service, I have implemented the IDispatchMessageInspector interface.
In the AfterReceiveRequest method ref Message request - the error "when reading the body: System.Xml.XmlException" comes. This error occurs due to an error in the XML in the request.I can't influence the request.
<data xsi:type="xsd:string"><?xml version="1.0" encoding="utf-8"?>
<someRequest>
<Number>Test</Number>
<Date>2023-01-09T00:00:00</Date>
</someRequest>
I'm trying to fix
request.toString().Replace("<?xml version="1.0" encoding="utf-8"?>" ,"");
Is it possible to get the xml body as text?
Tried:
using (var reader = request.GetReaderAtBodyContents())
{
query string var = reader.ReadContentAsString();
}
var body = request.getBody<string>();
If I implement the IDispatchOperationSelector interface. The method is called before AfterReceiveRequest. But there's also a Message parameter
To read an xml file, you can use XmlDocument.InnerXml to get it :
XmlDocument doc = new XmlDocument();
doc.Load("path to your file");
string xmlcontents = doc.InnerXml;
Or like this:
public string GetXMLAsString(XmlDocument myxml)
{
StringWriter sw = new StringWriter();
XmlTextWriter tx = new XmlTextWriter(sw);
myxml.WriteTo(tx);
string str = sw.ToString();//
return str;
}
After getting it, then get rid of what you don't need.
I am trying to serialize and deserialize responses I am getting from service. The response is xml. The problem is that response can contain namespaces.
This is a response example which contains xmlns in root element and xmlns:ns2 in child:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RootElement version="4.0" xmlns="http://www.test.com/test">
<Verification xmlns="" xmlns:ns2="http://www.test.com/test">
<!--some fields-->
</Verification>
</RootElement>
This is another response which does not contain xmlns and xmlns:n2:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RootElement version="2.0">
<Verification>
<!--some fields-->
</Verification>
</RootElement>
To deserialize this response and then to serialize again I am using this code:
public static object XmlDeserializeFromString(this string objectData, Type type)
{
var serializer = new XmlSerializer(type);
object result;
using (TextReader textReader = new StringReader(objectData))
{
using (XmlTextReader xmlReader = new XmlTextReader(textReader))
{
// xmlReader.Namespaces = false;
result = serializer.Deserialize(xmlReader);
}
}
return result;
}
public static string XmlSerializeToString(this object objectInstance, System.Text.Encoding encoding)
{
var serializer = new XmlSerializer(objectInstance.GetType());
var sb = new StringBuilder();
var settings = new XmlWriterSettings
{
Encoding = encoding ?? System.Text.Encoding.UTF8,
Indent = true,
NewLineHandling = NewLineHandling.Replace
};
using (var sw = new EncodedStringWriter(encoding, sb))
{
using (var xw = XmlWriter.Create(sw, settings))
{
serializer.Serialize(xw, objectInstance);
}
}
return sb.ToString();
}
The class: This works for first type or responses but not for second. If I'll remove Namespace from attribute it will work for second but not for first type.
[Serializable, XmlRoot("RootElement", IsNullable = false, Namespace="http://www.test.com/test")]
public class RootElement
{
[XmlAttribute("version")] public string Version;
[XmlElement(Namespace="http://www.test.com/test")]
public Verification[] Verification;
}
public class Verification
{
[XmlAttribute("vendor")] public string Vendor;
}
with xmlReader.Namespaces = false line uncommented deserialization fails for first example, with commented it fails for second example. I tried to override the NamespaceURL property from XmlTextReader to ignore namespaces like it is described here and in other questions and answers and articles, also tried to override Read() method but it does not helped. I also tried to add or remove urls from class attributes but it does not helped too.
Is it possible to create a serialize and deserialize methods which can do the job for both responses? What I am doing wrong?
Im trying to deserialize an array of objects from a XML Document.
The document built in the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<element>
.....
</element>
<element>
.....
</element>
</root>
But for some reason Im having lots of problems doing so.
This is my function which I call to deserialize it:
public static CardModel[] Load(string text)
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "root";
xRoot.IsNullable = true;
XmlSerializer serializer = new XmlSerializer(typeof(CardModel[]),xRoot);
StringReader reader = new StringReader(text);
CardModel[] o = serializer.Deserialize(reader) as CardModel[];
reader.Close();
return o;
}
And I am not sure if its done correctly or not. Because I know that in json you are unable to deserialize an array and you have to do some sort of "hack".
In the CardModel class (which is the element of the array) i use above the class the tag [XmlRoot("root")]. I have also tried to use [XmlRoot("element")] but still im getting stuck.
Afaik you can't directly deserialize into an array but would need a wrapper class like
[Serializable]
[XMLRoot("root")]
public class Root
{
// This does the magic of treating all "element" items nested under the root
// As part of this array
[XmlArray("element")]
public CardModel[] models;
}
And rather deserilialize into that like
public static CardModel[] Load(string text)
{
// I don't think that you need the attribute overwrite here
var serializer = new XmlSerializer(typeof(Root));
using(var reader = new StringReader(text))
{
var root = (Root) serializer.Deserialize(reader);
return root.models;
}
}
Why does XmlSerializer populate my object property with an XmlNode array when deserializing an empty typed element using XmlNodeReader instead of an empty string like it does when using StringReader (or XmlTextReader)?
The second assertion in the following code sample fails:
var doc = new XmlDocument();
doc.Load(new StringReader(#"
<Test xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Value xsi:type=""xsd:string"" />
</Test>"));
var ser = new XmlSerializer(typeof (Test));
var reader1 = new StringReader(doc.InnerXml);
var obj1 = (Test) ser.Deserialize(reader1);
Debug.Assert(obj1.Value is string);
var reader2 = new XmlNodeReader(doc.FirstChild);
var obj2 = (Test) ser.Deserialize(reader2);
Debug.Assert(obj2.Value is string);
public class Test
{
public object Value { get; set; }
}
I'm guessing it has something to do with the null internal NamespaceManager property but I'm not sure how to work around this mysterious limitation. How can I reliably deserialize a subset of my parsed XML document without converting it back into a string and re-parsing?
It looks like this is a very old XmlNodeReader bug that Microsoft have no intention of fixing. (Archived Microsoft Connect link here). I found a workaround on Lev Gimelfarb's blog here that adds namespaces to the reader's NameTable as prefixes are looked up.
public class ProperXmlNodeReader : XmlNodeReader
{
public ProperXmlNodeReader(XmlNode node) : base(node)
{
}
public override string LookupNamespace(string prefix)
{
return NameTable.Add(base.LookupNamespace(prefix));
}
}
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Head>
<h:talkId s:mustknow="1" xmlns:h="urn:schemas-test:testgate:hotel:2012-06">
sfasfasfasfsfsf</h:talkId>
</s:Head>
<s:Body>
<bookHotelResponse xmlns="urn:schemas-test:testgate:hotel:2012-06" xmlns:d="http://someURL" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d:bookingReference>123456</d:bookingReference>
<d:bookingStatus>successful</d:bookingStatus>
<d:price xmlns:p="moreURL">
<d:total>105</d:total>
</d:price>
</bookHotelResponse>
</s:Body>
</s:Envelope>
I am trying to read the above soap message XmlDocument using C#:
XmlDocument document = new XmlDocument();
document.LoadXml(soapmessage); //loading soap message as string
XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable);
manager.AddNamespace("d", "http://someURL");
XmlNodeList xnList = document.SelectNodes("//bookHotelResponse", manager);
int nodes = xnList.Count;
foreach (XmlNode xn in xnList)
{
Status = xn["d:bookingStatus"].InnerText;
}
The count is always zero and it is not reading the bookingstatus values.
BookHotelResponse is in the namespace urn:schemas-test:testgate:hotel:2012-06 (the default namespace in the sample xml) so you need to provide that namespace in your queries:
XmlDocument document = new XmlDocument();
document.LoadXml(soapmessage); //loading soap message as string
XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable);
manager.AddNamespace("d", "http://someURL");
manager.AddNamespace("bhr", "urn:schemas-test:testgate:hotel:2012-06");
XmlNodeList xnList = document.SelectNodes("//bhr:bookHotelResponse", manager);
int nodes = xnList.Count;
foreach (XmlNode xn in xnList)
{
Status = xn["d:bookingStatus"].InnerText;
}
Use LINQ2XML
To read bookingStatus,do this
XElement doc = XElement.Load("yourStream.xml");
XNamespace s = "http://schemas.xmlsoap.org/soap/envelope/";//Envelop namespace s
XNamespace bhr="urn:schemas-test:testgate:hotel:2012-06";//bookHotelResponse namespace
XNamespace d="http://someURL";//d namespace
foreach (var itm in doc.Descendants(s + "Body").Descendants(bhr+"bookHotelResponse"))
{
itm.Element(d+"bookingStatus").Value;//your bookingStatus value
}
LINQ2XML is cool though....:)
First you want to create a class to deseralize the xml values into
public class bookHotelResponse {
public int bookingReference { get; set; }
public int bookingStatus { get; set; }
}
Then you can utilize GetElementsByTagName to extract the body of the soap request and deseralize the request string into an object.
private static T DeserializeInnerSoapObject<T>(string soapResponse)
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(soapResponse);
var soapBody = xmlDocument.GetElementsByTagName("soap:Body")[0];
string innerObject = soapBody.InnerXml;
XmlSerializer deserializer = new XmlSerializer(typeof(T));
using (StringReader reader = new StringReader(innerObject))
{
return (T)deserializer.Deserialize(reader);
}
}
As I understand you want to get response from soap service. If so, you don't have to do all this hard work (making call, parsing xml, selecting nodes to get the response value) by yourself... instead you need to Add Service Reference to your project and it will do all the rest work for you, including generating class, making asmx call and so on...
Read more about it here https://msdn.microsoft.com/en-us/library/bb628649.aspx
Everything you'll need to do after adding reference is to invoke a class method something like this
var latestRates = (new GateSoapClient())?.ExchangeRatesLatest();
return latestRates?.Rates;