How to serialize to XmlWriter using YAXLib - c#

I'm trying to use YAXLib to serialize an object. If I serialize directly to a string it works fine, but if I try to serialize to an XmlWriter I get an empty xml.
This is a sample class declaration (nothing weird, right?):
public class City
{
public string Name { get; set; }
public long Population { get; set; }
}
This is what I'm doing to serialize it:
/* Object to serialize */
var city = new City() { Name = "Montevideo", Population = 1500000 };
var serializer = new YAXSerializer(typeof(City));
/* Serialize to XmlWriter */
var stringWriter = new StringWriter();
var xmlWriter = XmlWriter.Create(stringWriter);
serializer.Serialize(city, xmlWriter);
var result1 = stringWriter.ToString(); // result1 is ""
/* Serialize to String */
var result2 = serializer.Serialize(city); // result2 is "<City>...</City>"
I need to use the XmlWriter approach because I want to control several aspects of the resulting xml through XmlWriterSettings (omit xml declaration, avoid indentation, control new line handling, ...).
Anyone had successfully serialized to XmlWriter using YAXLib? What am I doing wrong?

A call to xmlWriter.Flush() may solve your problem, as it did on my own testing:
serializer.Serialize(city, xmlWriter);
xmlWriter.Flush();

Related

How to serialize a c# object with polymorphism to a soapmessage with overriden soapattributes, similar to XmlAttributeOverrides?

I have a dynamically created type, so I can't change it's implementation. I can't apply solutions like
[SoapInclude()]
Imagine if I had such classes:
public class BaseClass{}
public class DerivedClass:BaseClass{}
public class AnotherDerivedClass:BaseClass{}
public class RequestClass
{
public BaseClass item{get;set;}
}
If we try to just simply serialize we get the exception that DerivedClass was not found, so we have to override the xmlattributes.
I have managed to override my Type and serialize it with XmlAttributeOverrides like so:
var requestObject = new RequestClass() {item = new DerivedClass()};
XmlAttributes attrs = new XmlAttributes();
XmlElementAttribute attr = new XmlElementAttribute
{
ElementName = "DerivedClass",
Type = typeof(DerivedClass)
};
attrs.XmlElements.Add(attr);
XmlAttributeOverrides attoverrides = new XmlAttributeOverrides();
attoverrides.Add(typeof(RequestClass), "item", attrs);
XmlSerializer sr = new XmlSerializer(typeof(RequestClass), attoverrides);
StringWriter writer = new StringWriter();
sr.Serialize(writer, requestObject);
var xml = writer.ToString();
Now the above works, but what I want is to serialize my object as a soap-message. I have found similar classes to those above Like SoapAttributeOverrides and I've tried to override it with those classes and it doesn't seem to work. I've tried it like :
var requestObject = new RequestClass(){item = new DerivedClass()};
SoapAttributeOverrides ovr = new SoapAttributeOverrides();
SoapAttributes soapAtts = new SoapAttributes();
SoapElementAttribute element = new SoapElementAttribute();
element.ElementName = "DerivedClass";
ovr.Add(typeof(RequestClass), "item", soapAtts);
var sr = new XmlSerializer(new SoapReflectionImporter(ovr).ImportTypeMapping(typeof(RequestClass)));
StringWriter writer = new StringWriter();
sr.Serialize(writer, requestObject);
var xml = writer.ToString();
writer.Close();
I again get the exception DerivedClass was not found. How can I give the SoapElementAttribute a type similar to XmlElementAttribute.Type so that serialization can be made to support polymorphism for BaseClass via overrides?
SOAP-encoded XML serialization in .Net supports polymorphism via an included type mechanism. In order to add included types to a SoapReflectionImporter in runtime, use either:
SoapReflectionImporter.IncludeType(Type)
SoapReflectionImporter.IncludeTypes(ICustomAttributeProvider).
Thus you can manufacture your XmlSerializer as follows:
public static XmlSerializer RequestClassSerializer { get; }
= CreateSoapSerializerWithBaseClassIncludedTypes(typeof(RequestClass), typeof(DerivedClass), typeof(AnotherDerivedClass));
static XmlSerializer CreateSoapSerializerWithBaseClassIncludedTypes(Type rootType, params Type [] includedTypes)
{
var importer = new SoapReflectionImporter();
foreach (var type in includedTypes)
importer.IncludeType(type);
return new XmlSerializer(importer.ImportTypeMapping(rootType));
}
Then introduce the following extension method:
public static partial class XmlSerializationHelper
{
public static string GetSoapXml<T>(this T obj, XName wrapperName, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
xmlWriter.WriteStartElement(wrapperName.LocalName, wrapperName.NamespaceName);
(serializer ?? GetDefaultSoapSerializer(obj.GetType())).Serialize(xmlWriter, obj);
xmlWriter.WriteEndElement();
}
return textWriter.ToString();
}
}
static readonly Dictionary<Type, XmlSerializer> cache = new Dictionary<Type, XmlSerializer>();
public static XmlSerializer GetDefaultSoapSerializer(Type type)
{
lock(cache)
{
if (cache.TryGetValue(type, out var serializer))
return serializer;
return cache[type] = new XmlSerializer(new SoapReflectionImporter().ImportTypeMapping(type));
}
}
}
And you will be able to serialize your requestObject as follows:
var requestObject = new RequestClass(){item = new DerivedClass()};
var xml = requestObject.GetSoapXml("wrapper", RequestClassSerializer);
Notes:
To avoid a severe memory leak you should statically cache any XmlSerializer not created with the XmlSerializer(Type) or XmlSerializer(Type, String) constructors. For why, see the documentation and also Memory Leak using StreamReader and XmlSerializer.
If you are dynamically generating families of types you may need to cache their serializers in a static dictionary protected by lock statements.
As explained in Extension method to serialize generic objects as a SOAP formatted stream and InvalidOperationException while SOAP serialization of complex type you need to manually write an envelope or other wrapper element before serializing directly to XML using a SOAP XML serializer.
If for some reason you just want the SOAP XML body as a series of XML fragments, you can do:
public static string GetSoapBodyXml<T>(this T obj, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true, ConformanceLevel = ConformanceLevel.Fragment }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
xmlWriter.WriteWhitespace(""); // Hack to prevent an error about WriteStartDocument getting called for ConformanceLevel.Fragment
(serializer ?? GetDefaultSoapSerializer(obj.GetType())).Serialize(xmlWriter, obj);
}
return textWriter.ToString();
}
}
Demo fiddle here.

C# Serialize object to SOAP string array issue

I have an issue where trying to serialize an object containing a string array to soap causes an exception in my application. I am doing the following to create the soap formatter:
XmlTypeMapping mapping = new SoapReflectionImporter().ImportTypeMapping(obj.GetType());
XmlSerializer serializer = new XmlSerializer(mapping);
when I call Serialize on the serializer I get the following exception. "Token StartElement in state Epilog would result in an invalid XML document."
However if I just want regular xml and create my XmlSerializer like this:
XmlSerializer serializer = new XmlSerializer(obj.GetType());
Everything works fine and the xml contains the string array.
I have a full example below that reproduces the issue on my machine if someone could take a look I would be very grateful as I am out of ideas!
static void Main(string[] args)
{
GetAlarmEventTypesResponse bob = new GetAlarmEventTypesResponse();
bob.GetAlarmEventTypesTypes = new string[] { "bob", "bob1", "bob2" };
bob.version = "2.0";
// works
string xml = GetRegularDocument(bob);
Console.WriteLine(xml);
// throws exception
string soap = GetSoapDocument(bob);
Console.WriteLine(soap);
}
//------------------------------------------------------------------------------
[System.Xml.Serialization.SoapTypeAttribute(Namespace = "http://example/common/dataexchange/2011/05")]
public class GetAlarmEventTypesResponse
{
public GetAlarmEventTypesResponse()
{
version = "1.2";
}
[System.Xml.Serialization.XmlArrayItemAttribute("Type", IsNullable = false)]
public string[] GetAlarmEventTypesTypes { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string version { get; set; }
}
//------------------------------------------------------------------------------
public static string GetRegularDocument(object obj)
{
string document = null;
XmlSerializer serializer = new XmlSerializer(obj.GetType());
using (StringWriter textWriter = new StringWriter())
{
serializer.Serialize(textWriter, obj);
document = textWriter.ToString();
}
return document;
}
//------------------------------------------------------------------------------
public static string GetSoapDocument(object obj)
{
string document = null;
XmlTypeMapping mapping = new SoapReflectionImporter().ImportTypeMapping(obj.GetType());
XmlSerializer serializer = new XmlSerializer(mapping);
using (StringWriter textWriter = new StringWriter())
{
serializer.Serialize(textWriter, obj);
document = textWriter.ToString();
}
return document;
}

C# serialize object to XML with element containing XML text without escaping

I searched and tried some attributes but none of them worked for my below scenario:
public class ObjSer
{
[XmlElement("Name")]
public string Name
{
get; set;
}
}
//Code to serialize
var obj = new ObjSer();
obj.Name = "<tag1>Value</tag1>";
var stringwriter = new System.IO.StringWriter();
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(stringwriter, obj);
Output would be as follows:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
But I need output as:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
Scenario 2: In some cases I need to set:
obj.Name = "Value";
Is there any attribute or method I can override to make it possible?
You can't with default serializer. XmlSerializer does encoding of all values during serialization.
If you want your member to hold xml value, it must be XmlElement. Here is how you can accomplish it
public class ObjSer
{
[XmlElement("Name")]
public XmlElement Name
{
get; set;
}
}
var obj = new ObjSer();
// <-- load xml
var doc = new XmlDocument();
doc.LoadXml("<tag1>Value</tag1>");
obj.Name = doc.DocumentElement;
// --> assign the element
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
Output:
<?xml version="1.0" encoding="IBM437"?>
<ObjSer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>
<tag1>Value</tag1>
</Name>
</ObjSer>
UPDATE:
In case if you want to use XElement instead of XmlElement, here is sample on how to do it
public class ObjSer
{
[XmlElement("Name")]
public XElement Name
{
get; set;
}
}
static void Main(string[] args)
{
//Code to serialize
var obj = new ObjSer();
obj.Name = XDocument.Parse("<tag1>Value</tag1>").Document.FirstNode as XElement;
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
}
No, you can't. It is the natural and usual behaviour of the xml serializer. The serializer doesn't have to handle within the XML strings. So, it escapes the xml as expected.
You should decode the escaped string again while deserializing it.
If you want to add dynamically elements in the XML I suggest you to use Linq to XML and you can create tag1 or another kind of elements easily.
I would suggest serializing to an XDocument then converting that to a string and manually unescaping the desired part and writing it to a file. I would say this would give you the least headache it shouldn't be more than a couple lines of code. If you need it I can provide some code example if needed.
I found one more way of changing the type
public class NameNode
{
public string tag1
{
get; set;
}
[XmlText]
public string Value
{
get; set;
}
}
public class ObjSer
{
[XmlElement("Name")]
public NameNode Name
{
get; set;
}
}
To set value of Name:
var obj = new ObjSer();
var valueToSet = "<tag1>Value</tag1>";
//or var valueToSet = "Value";
//With XML tag:
if (valueToSet.Contains("</"))
{
var doc = new XmlDocument();
doc.LoadXml(valueToSet);
obj.Name.tag1 = doc.InnerText;
}
else //Without XML Tags
{
obj.Name.Value = senderRecipient.Name;
}
This solution will work in both cases, but has limitation. It will work only for predefined tags (ex. tag1)

XML Serializing and Deserializing List<string> in c#

i have an object of following class
[XmlRoot("http://schemas.abc.com")]
[DataContract]
public class Employee
{
[XmlAttribute]
[DataMember]
public List<string> Positions { get; set; }
}
Positions contains following two strings "Project Manager" and "Senior Project Manager"
I serialized this object through following method and saved in DB
public static string Serialize(Employee entity)
{
var memoryStream = new MemoryStream();
var serializer = new XmlSerializer(typeof(Employee));
using (var xmlTextWriter = new XmlTextWriter(memoryStream,
Encoding.Unicode))
{
serializer.Serialize(xmlTextWriter, entity);
xmlTextWriter.Close();
}
return UnicodeByteArrayToString(memoryStream.ToArray());
}
private static string UnicodeByteArrayToString(byte[] input)
{
var constructedString = Encoding.Unicode.GetString(input);
return constructedString;
}
Then i am retrieving and deserializing the object through following method
public static Employee Deserialize(string str)
{
var data = StringToUnicodeByteArray(str);
var memoryStream = new MemoryStream(data);
var serializer = new XmlSerializer(typeof(Employee));
var xmlTextReader = new XmlTextReader(memoryStream);
return (Employee)serializer.Deserialize(xmlTextReader);
}
private static byte[] StringToUnicodeByteArray(string input)
{
var byteArray = Encoding.Unicode.GetBytes(input);
return byteArray;
}
Now i am getting 5 objects of strings in position list
"Project", "Manager", "Senior", "Project", "Manager"
deserialization is creating new string object after every space
Quick help will be much appreciated
Answer from Eugene Podskal
think that you should remove the XmlAttribute attribute. XmlSerializer doesn't know how to parse values in attribute string other than by splitting it on whitespace. Or you can create a property that will give you String that allows to unambiguously separate individual items in it (like with commas or whatever), and that can parse individual items from the String it is set to. This propery will be serializable, while actual List will be XmlIgnore

How to convert a List<T> to specific Json format

I want to be able to convert a List<T> into a specific JSON table-like format. In my case, the T will always be a simple object (no nested properties). Here are two examples to illustrate what I want.
Example #1: List<Person> to JSON
// C# list of Persons
var list = new List<Person>() {
new Person() { First = "Jesse", Last = "Gavin", Twitter = "jessegavin" },
new Person() { First = "John", Last = "Sheehan", Twitter = "johnsheehan" }
};
// I want to transform the list above into a JSON object like so
{
columns : ["First", "Last", "Twitter"],
rows: [
["Jesse", "Gavin", "jessegavin"],
["John", "Sheehan", "johnsheehan"]
]
}
Example #2: List<Address> to JSON
// C# list of Locations
var list = new List<Location>() {
new Location() { City = "Los Angeles", State = "CA", Zip = "90210" },
new Location() { City = "Saint Paul", State = "MN", Zip = "55101" },
};
// I want to transform the list above into a JSON object like so
{
columns : ["City", "State", "Zip"],
rows: [
["Los Angeles", "CA", "90210"],
["Saint Paul", "MN", "55101"]
]
}
Is there a way to tell JSON.net to serialize an object in this manner? If not, how could I accomplish this? Thanks.
UPDATE:
Thanks to #Hightechrider's answer, I was able to write some code that solves the problem.
You can view a working example here https://gist.github.com/1153155
Using reflection you can get a list of properties for the type:
var props = typeof(Person).GetProperties();
Given an instance of a Person p you can get an enumeration of the property values thus:
props.Select(prop => prop.GetValue(p, null))
Wrap those up in a generic method, add your favorite Json serialization and you have the format you want.
Assuming your using .Net 4 this should do everything you want. The class actually lets you convert to either XML or JSON. The Enum for CommunicationType is at the bottom. The serializer works best if the class your passing it has been decorated with DataContract & DataMember attributes. I've included a sample at the bottom. It will also take an anonymous type so long as it's all simple types.
Reflection would work as well but then you have to understand all the JSON nuances to output complex data types, etc. This used the built-in JSON serializer in .Net 4. One more note, because JSON does not define a date type .Net puts dates in a funky ASP.Net custom format. So long as your deserializing using the built-in deserializer it works just fine. I can dig up the documentation on that if you need.
using System;
using System.Xml.Serialization;
using System.Text;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Xml.Linq;
internal class Converter
{
public static string Convert<T>(T obj, CommunicationType format, bool indent = false, bool includetype = false)
{
if (format == CommunicationType.XML)
{
return ToXML<T>(obj, includetype, indent);
}
else if (format == CommunicationType.JSON)
{
return ToJSON<T>(obj);
}
else
{
return string.Empty;
}
}
private static string ToXML<T>(T obj, bool includetype, bool indent = false)
{
if (includetype)
{
XElement xml = XMLConverter.ToXml(obj, null, includetype);
if(indent) {
return xml.ToString();
}
else
{
return xml.ToString(SaveOptions.DisableFormatting);
}
}
else
{
System.Xml.Serialization.XmlSerializerNamespaces ns = new System.Xml.Serialization.XmlSerializerNamespaces();
XmlSerializer xs = new XmlSerializer(typeof(T));
StringBuilder sbuilder = new StringBuilder();
var xmlws = new System.Xml.XmlWriterSettings() { OmitXmlDeclaration = true, Indent = indent };
ns.Add(string.Empty, string.Empty);
using (var writer = System.Xml.XmlWriter.Create(sbuilder, xmlws))
{
xs.Serialize(writer, obj, ns);
}
string result = sbuilder.ToString();
ns = null;
xs = null;
sbuilder = null;
xmlws = null;
return result;
}
}
private static string ToJSON<T>(T obj)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
using (MemoryStream ms = new MemoryStream())
{
string result = string.Empty;
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
ser.WriteObject(ms, obj);
result = encoding.GetString(ms.ToArray());
ms.Close();
encoding = null;
ser = null;
return result;
}
}
}
[DataContract()]
public enum CommunicationType : int
{
[XmlEnum("0"), EnumMember(Value = "0")]
XML = 0,
[XmlEnum("1"), EnumMember(Value = "1")]
JSON = 1
}
[DataContract(Namespace = "")]
public partial class AppData
{
[DataMember(Name = "ID")]
public string ID { get; set; }
[DataMember(Name = "Key")]
public string Key { get; set; }
[DataMember(Name = "Value")]
public string Value { get; set; }
[DataMember(Name = "ObjectType")]
public string ObjectType { get; set; }
}
Any specific reason why you don't need the standard format?
To actually answer the question:
Since this is something that is outside of JSON syntax I can't think of a way to implement this within the default framework.
One solution would be to leverage attributes decorate the properties you want transported over the wired with a custom attribute and using Reflection cycle through the properties and output their property names as the column headers and then cycle throw the objects and write the values. Generic enough so it could be applied across other objects as well.
public class Location
{
[JsonFooAttribute("City")]
public string city {get;set;}
[JsonFooAttribute("State")]
public string state {get;set;}
}

Categories