In order to return useful information in SoapException.Detail for an asmx web service, I took an idea from WCF and created a fault class to contain said useful information. That fault object is then serialised to the required XmlNode of a thrown SoapException.
I'm wondering whether I have the best code to create the XmlDocument - here is my take on it:
var xmlDocument = new XmlDocument();
var serializer = new XmlSerializer(typeof(T));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, theObjectContainingUsefulInformation);
stream.Flush();
stream.Seek(0, SeekOrigin.Begin);
xmlDocument.Load(stream);
}
Is there a better way of doing this?
UPDATE: I actually ended up doing the following, because unless you wrap the XML in a <detail> xml element, you get a SoapHeaderException at the client end:
var serialiseToDocument = new XmlDocument();
var serializer = new XmlSerializer(typeof(T));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, e.ExceptionContext);
stream.Flush();
stream.Seek(0, SeekOrigin.Begin);
serialiseToDocument.Load(stream);
}
// Remove the xml declaration
serialiseToDocument.RemoveChild(serialiseToDocument.FirstChild);
// Memorise the node we want
var serialisedNode = serialiseToDocument.FirstChild;
// and wrap it in a <detail> element
var rootNode = serialiseToDocument.CreateNode(XmlNodeType.Element, "detail", "");
rootNode.AppendChild(serialisedNode);
UPDATE 2: Given John Saunders excellent answer, I've now started using the following:
private static void SerialiseFaultDetail()
{
var fault = new ServiceFault
{
Message = "Exception occurred",
ErrorCode = 1010
};
// Serialise to the XML document
var detailDocument = new XmlDocument();
var nav = detailDocument.CreateNavigator();
if (nav != null)
{
using (XmlWriter writer = nav.AppendChild())
{
var ser = new XmlSerializer(fault.GetType());
ser.Serialize(writer, fault);
}
}
// Memorise and remove the element we want
XmlNode infoNode = detailDocument.FirstChild;
detailDocument.RemoveChild(infoNode);
// Move into a root <detail> element
var rootNode = detailDocument.AppendChild(detailDocument.CreateNode(XmlNodeType.Element, "detail", ""));
rootNode.AppendChild(infoNode);
Console.WriteLine(detailDocument.OuterXml);
Console.ReadKey();
}
EDIT: Creates output inside of detail element
public class MyFault
{
public int ErrorCode { get; set; }
public string ErrorMessage { get; set; }
}
public static XmlDocument SerializeFault()
{
var fault = new MyFault
{
ErrorCode = 1,
ErrorMessage = "This is an error"
};
var faultDocument = new XmlDocument();
var nav = faultDocument.CreateNavigator();
using (var writer = nav.AppendChild())
{
var ser = new XmlSerializer(fault.GetType());
ser.Serialize(writer, fault);
}
var detailDocument = new XmlDocument();
var detailElement = detailDocument.CreateElement(
"exc",
SoapException.DetailElementName.Name,
SoapException.DetailElementName.Namespace);
detailDocument.AppendChild(detailElement);
detailElement.AppendChild(
detailDocument.ImportNode(
faultDocument.DocumentElement, true));
return detailDocument;
}
Related
I am a bit stuck with this issue. When running XDocument.Load with a file from disk, the call completes within milliseconds. But if we make the call using the same data but from a XMLNodeReader, it takes upwards of 6 minutes. Any help would be appreciated. Thanks!
See the code below:
XDcoument.Load from File
private static string SerializeData(Data data)
{
var serializer = new XmlSerializer(typeof(Data));
var dataXmlDocument = new XmlDocument();
using (XmlWriter writer = dataXmlDocument.CreateNavigator().AppendChild())
{
serializer.Serialize(writer, data);
}
dataXmlDocument.Save("C:/test.xml");
var dataXDocument = new XDocument();
dataXDocument = XDocument.Load(File.OpenRead("C:\test.xml");
}
XDocument.Load from XMLNodeReader
private static string SerializeData(Data data)
{
var serializer = new XmlSerializer(typeof(Data));
var dataXmlDocument = new XmlDocument();
using (XmlWriter writer = dataXmlDocument.CreateNavigator().AppendChild())
{
serializer.Serialize(writer, data);
}
var dataXDocument = new XDocument();
using (var nodeReader = new XmlNodeReader(dataXmlDocument))
{
nodeReader.MoveToContent();
dataXDocument = XDocument.Load(nodeReader);
}
}
I am trying to serialize a class in c# but the output is not quite I am after. I want to get rid of one element in output xml - class name - that comes along with serialization.
My class is:
[XmlType("ADSobjotsing")]
public class ObjKompParam
{
[XmlElement("aadressTekst")]
public string Tekst;
[XmlElement("adsOid")]
public string OID;
My code is:
protected override XElement ComposeQueryBody(object InputParams)
{
ObjKompParam param = (ObjKompParam)InputParams;
var ads_o_q = new ObjKompParam();
XElement body = new XElement(SOAPNS + "Body",
new XElement(prod + "ADSobjotsing"));
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer serializer = new XmlSerializer(typeof(ObjKompParam),"");
XElement xe;
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, param, ns);
stream.Position = 0;
using (XmlReader reader = XmlReader.Create(stream))
{
xe = XElement.Load(reader);
}
}
body.Descendants(prod + "ADSobjotsing").First().Add(new XElement(xe));
return body;
}
The output I get is:
<SOAP-ENV:Body>
<prod:ADSobjotsing>
<ADSobjotsing>
<aadressTekst>Sügise 10</aadressTekst>
</ADSobjotsing>
</prod:ADSobjotsing>
</SOAP-ENV:Body>
The xml output (body) I am after is following:
<SOAP-ENV:Body>
<prod:ADSobjotsing>
<aadressTekst>Sügise 10</aadressTekst>
</prod:ADSobjotsing>
</SOAP-ENV:Body>
I answer myself, as I worked out the solution:
XmlSerializer serializer = new XmlSerializer(typeof(ObjKompParam),"");
XElement xe;
using (var stream = new MemoryStream()) //write into stream
{
serializer.Serialize(stream, param, ns); //writer, object
stream.Position = 0;
using (XmlReader reader = XmlReader.Create(stream))
{
xe = XElement.Load(reader);
}
}
var child = xe.Descendants();
body.Descendants(prod + "ADSobjotsing").First().Add(child);
return body;
I made the new variable for object class ObjKompParam children and added children as descendants.
I have spent countless hours working on this issue, and have tried so many different things. Some background... I am working within an SSIS module using C# code to attempt to deserialize a SOAP XML stream. I have been able to deserialize XML streams before, but this one is significantly more complicated than the previous ones I have been involved with.
I have verified that the string I am using to populate my stream is correct for the results, and I am reaching the end of my rope.
Here is the method I am using to try to deserialize. You can see I have included several commented out sections to show some of the other methods I have used to try to make this work:
private Envelope GetWebServiceResultFromStream(StreamReader str)
{
bool b = true;
Envelope xmlResponse = null;
String xmlPayload = "";
//Deserialize our XML
try
{
//System.Runtime.Serialization.Formatters.Soap.SoapFormatter soapFormatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
//using (Stream savestream = str.BaseStream)
//{
// xmlResponse = (Envelope)soapFormatter.Deserialize(savestream);
//}
System.Xml.Serialization.XmlSerializer sr = new System.Xml.Serialization.XmlSerializer(typeof(Envelope));
using (Stream savestream = str.BaseStream)
{
StreamReader streamreader = new StreamReader(savestream, Encoding.UTF8);
xmlPayload = streamreader.ReadToEnd();
//xmlPayload = System.Security.SecurityElement.Escape(xmlPayload);
//xmlPayload = xmlPayload.Replace("&", "&");
//xmlPayload = xmlPayload.Replace("&", "&");
xmlPayload = xmlPayload.Replace("'", "'");
//xmlPayload = xmlPayload.Replace("soap:Envelope", "Envelope");
File.WriteAllText(#"myxml.xml",xmlPayload);
byte[] byteArray = Encoding.UTF8.GetBytes(xmlPayload);
MemoryStream secondstream = new MemoryStream(byteArray);
secondstream.Position = 0;
xmlResponse = sr.Deserialize(secondstream) as Envelope;
//xmlResponse = (Envelope)DeserializeFromXml<Envelope>(xmlPayload);
//XmlDocument doc = new XmlDocument();
//doc.Load(secondstream);
//XmlNodeReader reader = new XmlNodeReader(doc);
//using (reader)
//{
// xmlResponse = sr.Deserialize(reader) as Envelope;
//}
//System.Runtime.Serialization.Formatters.Soap.SoapFormatter soapFormatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
//xmlResponse = (Envelope)soapFormatter.Deserialize(secondstream);
}
//XmlDocument xmlSoapRequest = new XmlDocument();
//using (Stream savestream = str.BaseStream)
//{
// using (StreamReader readStream = new StreamReader(savestream, Encoding.UTF8))
// {
// xmlSoapRequest.Load(readStream);
// xmlPayload = xmlSoapRequest.SelectSingleNode("//Envelope/Body/GetRequisitionByDateResponse/").InnerText;
// }
//}
}
catch (Exception ex)
{
DumpException(ex);
this.ComponentMetaData.FireInformation(54, "", "Failed to deserialize: " + ex.Message + " Inner: " + ex.InnerException + " Source: " + ex.Source, "", 43, ref b);
}
return xmlResponse;
}
public static T DeserializeFromXml<T>(string xml)
{
T result;
var ser = new XmlSerializer(typeof(T));
using (var tr = new StringReader(xml))
{
result = (T)ser.Deserialize(tr);
}
return result;
}
I am using c# classes generated by feeding a response into the XML to C# converter (http://xmltocsharp.azurewebsites.net/). Unfortunately it appears the classes are too complex to put here (posts have a 30k character limit), so it has been pasted here:
http://txt.do/d91eo
I also pasted an example response:
http://txt.do/d91eb
I have consulted the wsdl and it appears that many DateTime fields are being read as strings through the converter. I am not sure if this is an issue, but I have tried to do my best to replace those data types and the error persisted.
Here is a screenshot of my error:
http://imgur.com/a/objJq
Other things I have tried:
Replacing invalid characters throughout the xml document (I found that it is doing this already, except in the case of some of the "'" characters).
Removed namespaces.
Marked all the classes as Serializable.
The Web Services object within SSIS (throws an error and doesn't let me enter any variables to send).
Regenerated the class list with a number of variants of the return result.
Found it
First add UTF8 to StreamReader : new StreamReader(FILENAME, Encoding.UTF8);
Here is your Root Class
[XmlRoot(ElementName = "Envelope", Namespace = "http://www.w3.org/2003/05/soap-envelope")]
public class Envelope
{
}
Here is you first line of XML
<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">
Compare Namespaces
The following code is running without exceptions. I can't include class because of size limitation. Note the namespace for Class Envelope I replaced "-" with "/". The class Envelope is constructed but all properties are null's because the namespace don't agree with the xml.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication50
{
class Program
{
static void Main(string[] args)
{
new Test();
}
}
public class Test
{
const string FILENAME = #"c:\temp\test2.xml";
public Test()
{
StreamReader reader = new StreamReader(FILENAME, Encoding.UTF8);
GetWebServiceResultFromStream(reader);
}
private Envelope GetWebServiceResultFromStream(StreamReader str)
{
bool b = true;
Envelope xmlResponse = null;
String xmlPayload = "";
//Deserialize our XML
try
{
//System.Runtime.Serialization.Formatters.Soap.SoapFormatter soapFormatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
//using (Stream savestream = str.BaseStream)
//{
// xmlResponse = (Envelope)soapFormatter.Deserialize(savestream);
//}
System.Xml.Serialization.XmlSerializer sr = new System.Xml.Serialization.XmlSerializer(typeof(Envelope));
using (Stream savestream = str.BaseStream)
{
StreamReader streamreader = new StreamReader(savestream, Encoding.UTF8);
xmlPayload = streamreader.ReadToEnd();
//xmlPayload = System.Security.SecurityElement.Escape(xmlPayload);
//xmlPayload = xmlPayload.Replace("&", "&");
//xmlPayload = xmlPayload.Replace("&", "&");
xmlPayload = xmlPayload.Replace("'", "'");
//xmlPayload = xmlPayload.Replace("soap:Envelope", "Envelope");
File.WriteAllText(#"myxml.xml",xmlPayload);
byte[] byteArray = Encoding.UTF8.GetBytes(xmlPayload);
MemoryStream secondstream = new MemoryStream(byteArray);
secondstream.Position = 0;
xmlResponse = sr.Deserialize(secondstream) as Envelope;
//xmlResponse = (Envelope)DeserializeFromXml<Envelope>(xmlPayload);
//XmlDocument doc = new XmlDocument();
//doc.Load(secondstream);
//XmlNodeReader reader = new XmlNodeReader(doc);
//using (reader)
//{
// xmlResponse = sr.Deserialize(reader) as Envelope;
//}
//System.Runtime.Serialization.Formatters.Soap.SoapFormatter soapFormatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
//xmlResponse = (Envelope)soapFormatter.Deserialize(secondstream);
}
//XmlDocument xmlSoapRequest = new XmlDocument();
//using (Stream savestream = str.BaseStream)
//{
// using (StreamReader readStream = new StreamReader(savestream, Encoding.UTF8))
// {
// xmlSoapRequest.Load(readStream);
// xmlPayload = xmlSoapRequest.SelectSingleNode("//Envelope/Body/GetRequisitionByDateResponse/").InnerText;
// }
//}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
//DumpException(ex);
//this.ComponentMetaData.FireInformation(54, "", "Failed to deserialize: " + ex.Message + " Inner: " + ex.InnerException + " Source: " + ex.Source, "", 43, ref b);
}
return xmlResponse;
}
public static T DeserializeFromXml<T>(string xml)
{
T result;
var ser = new XmlSerializer(typeof(T));
using (var tr = new StringReader(xml))
{
result = (T)ser.Deserialize(tr);
}
return result;
}
}
[XmlRoot(ElementName = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class Envelope
{
[XmlElement(ElementName = "Body", Namespace = "http://www.w3.org/2003/05/soap-envelope")]
public Body Body { get; set; }
[XmlAttribute(AttributeName = "soap", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Soap { get; set; }
[XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsi { get; set; }
[XmlAttribute(AttributeName = "xsd", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsd { get; set; }
}
}
I have Converted a the Different entities in a List and now I have a master list containing the many list of different models.
public static string xmlSerialize(List<Object> o)
{
XmlDocument xmlOut = new XmlDocument();
foreach(var i in o)
{
using (MemoryStream xmlStream = new MemoryStream())
{
//MemoryStream myMemStr = new MemoryStream();
XmlSerializer xmlSerializer = new XmlSerializer(i.GetType());
xmlSerializer.Serialize(xmlStream, i);
xmlStream.Position = 0;
xmlOut.Load(xmlStream);
}
}
return xmlOut.InnerXml;
}
The above method converts the multiple List into a XML. But this code Overwrites the previous XML string in every loop run.
Is There any solution to get the single XML for every entity.
IMHO - the simplest solution here is to load xml into temporary XmlDocument and then perform Import
Idea is the following
public static string xmlSerialize(List<Object> o)
{
XmlDocument xmlOut = new XmlDocument();
foreach(var i in o)
{
var tmpDoc = new XmlDocument();
using (MemoryStream xmlStream = new MemoryStream())
{
//MemoryStream myMemStr = new MemoryStream();
XmlSerializer xmlSerializer = new XmlSerializer(i.GetType());
xmlSerializer.Serialize(xmlStream, i);
xmlStream.Position = 0;
tmpDoc.Load(xmlStream);
}
var newNode = xmlOut.ImportNode(tmpDoc.DocumentElement.LastChild, true);
xmlOut.DocumentElement.AppendChild(newNode);
}
return xmlOut.InnerXml;
}
I want to add
xsi:noNamespaceSchemaLocation="FullModeDataset.xsd"
and
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
as attibutes to my root node "ApplicationData" so the root node will look like this..
<ApplicationData
xsi:noNamespaceSchemaLocation="FullModeDataset.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
I am creating the xml from a string, outputing a string that is proper xml with this code..
var doc = new XmlDocument();
doc.LoadXml(myInputXmlString);
var ms = new MemoryStream();
var tx = XmlWriter.Create(ms,
new XmlWriterSettings
{
OmitXmlDeclaration = false,
ConformanceLevel = ConformanceLevel.Document,
Encoding = UTF8Encoding.UTF8
});
doc.Save(tx);
//I TRIED THE COMMENTED CODE BELOW BUT WITH NO SUCCESS
//XmlAttribute newAttr = doc.CreateAttribute("xsi:noNamespaceSchemaLocation");
//newAttr.Value = "FullModeDataset.xsd";
//XmlElement applicationNode = doc.DocumentElement["AppicationData"];
//applicationNode.Attributes.Append(newAttr);
//doc.Save(tx);
var xmlString = UTF8Encoding.UTF8.GetString(ms.ToArray());
how do I add these attributes to my xml string?
You need to create the attribute using the overload which takes the prefix and the namespace URL of the attribute you want to create, as shown below:
public class StackOverflow_14128649
{
public static void Test()
{
string myInputXmlString = #"<ApplicationData>
<something>else</something>
</ApplicationData>";
var doc = new XmlDocument();
doc.LoadXml(myInputXmlString);
XmlAttribute newAttr = doc.CreateAttribute(
"xsi",
"noNamespaceSchemaLocation",
"http://www.w3.org/2001/XMLSchema-instance");
newAttr.Value = "FullModeDataset.xsd";
doc.DocumentElement.Attributes.Append(newAttr);
var ms = new MemoryStream();
XmlWriterSettings ws = new XmlWriterSettings
{
OmitXmlDeclaration = false,
ConformanceLevel = ConformanceLevel.Document,
Encoding = UTF8Encoding.UTF8
};
var tx = XmlWriter.Create(ms, ws);
doc.Save(tx);
tx.Flush();
var xmlString = UTF8Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine(xmlString);
}
}