Get the XMlNode content from object marked as serializable - c#

I have a class Image implementing ISerializable:
[Serializable]
[XmlRoot(ElementName = "IMAGE")]
[TypeConverter(typeof(ImageTypeConverter))]
public class ImageResource : ISerializable {
[XmlAttribute(AttributeName = "TYPE")]
public string Extension{
get;
set;
}
}
I just want to know if we can get the xml node for an object of this class?
Suppose this object is serializes as
<IMAGE TYPE=".mpg"/>
I want to get this Node content as string.

The XML representation does not exist until you serialize the instance. Once you serialize it, you can manipulate it as XML.
Hint: you should be able to serialize directly into an XDocument:
XDocument doc = new XDocument();
using (XmlWriter writer = doc.CreateNavigator().AppendChild())
{
XmlSerializer ser = new XmlSerializer(typeof(ImageResource));
ser.Serialize(instance);
}

Never mind got the answer from below
link text

Related

XML serialisation for class properties with additional meta data

I have an entity as below
public class Vehicle{
public int VehicleId {get;set;};
public string Make {get;set;};
public string Model{get;set;}
}
I wanted to serialize as below
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common" >HXV</Make>
<Model AppliesTo="C2">34-34</Model>
</Vehicle>
I have around 100 properties like this in Vehicle class, for each vehicle property I wanted to attach a metadata ApplieTo which will be helpful to downstream systems. AppliesTo attribute is static and its value is defined at the design time. Now How can I attach AppliesTo metadata to each property and inturn get serialized to XML?
You can use XElement from System.Xml.Linq to achieve this. As your data is static you can assign them easily. Sample code below -
XElement data= new XElement("Vehicle",
new XElement("VehicleId", new XAttribute("AppliesTo", "C1"),"1244"),
new XElement("Make", new XAttribute("AppliesTo", "Common"), "HXV"),
new XElement("Model", new XAttribute("AppliesTo", "C2"), "34 - 34")
);
//OUTPUT
<Vehicle>
<VehicleId AppliesTo="C1">1244</VehicleId>
<Make AppliesTo="Common">HXV</Make>
<Model AppliesTo="C2">34 - 34</Model>
</Vehicle>
If you are not interested in System.Xml.Linq then you have another option of XmlSerializer class. For that you need yo define separate classes for each property of vehicle. Below is the sample code and you can extend the same for Make and Model -
[XmlRoot(ElementName = "VehicleId")]
public class VehicleId
{
[XmlAttribute(AttributeName = "AppliesTo")]
public string AppliesTo { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "Vehicle")]
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")]
public VehicleId VehicleId { get; set; }
//Add other properties here
}
Then create test data and use XmlSerializer class to construct XML -
Vehicle vehicle = new Vehicle
{
VehicleId = new VehicleId
{
Text = "1244",
AppliesTo = "C1",
}
};
XmlSerializer testData = new XmlSerializer(typeof(Vehicle));
var xml = "";
using (var sww = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sww))
{
testData.Serialize(writer, vehicle);
xml = sww.ToString(); // XML
}
}
It is not easy or ideal to use the default .NET XML serializer (System.Xml.Serialization.XmlSerializer) in the way you want, but it's possible. This answer shows how to create a class structure to hold both your main data and the metadata, then use XmlAttributeAttribute to mark a property so it gets serialized as an XML attribute.
Assumptions:
There are a number of unknowns about your intended implementation, such as:
The XML serializer you want to use (default for .NET?)
The mechanism to inject 'AppliesTo' (attribute?)
Do you care about deserialization?
This answer assumes the default .NET serializer, that deserialization matters, and that you don't care about the exact method of injecting your metadata.
Key concepts:
A generic class to hold both our main property value and the metadata (see PropertyWithAppliesTo<T>)
Using XmlAttributeAttribute on the generic class' metadata, so it is written as an XML attribute on the parent property
Using XmlTextAttribute on the generic class' main data, so it is written as the Xml text of the parent property (and not as a sub-property)
Including two properties on the main type being serialized (in this case Vehicle) for every value you want serialized: one of the new generic type that gets serialized with metadata, and one of the original type marked with XmlIgnoreAttribute that provides 'expected' access to the property's value
Using the XmlElementAttribute to change the name of the serialized property (so it matches the expected name)
Code:
using System;
using System.IO;
using System.Xml.Serialization;
namespace SomeNamespace
{
public class Program
{
static void Main()
{
var serializer = new XmlSerializer(typeof(Vehicle));
string s;
var vehicle = new Vehicle { VehicleId = 1244 };
//serialize
using (var writer = new StringWriter())
{
serializer.Serialize(writer, vehicle);
s = writer.ToString();
Console.WriteLine(s);
}
// edit the serialized string to test deserialization
s = s.Replace("Common", "C1");
//deserialize
using (var reader = new StringReader(s))
{
vehicle = (Vehicle)serializer.Deserialize(reader);
Console.WriteLine($"AppliesTo attribute for VehicleId: {vehicle.VehicleIdMeta.AppliesTo}");
}
}
}
public class Vehicle
{
[XmlElement(ElementName = "VehicleId")] // renames to remove the 'Meta' string
public PropertyWithAppliesTo<int> VehicleIdMeta { get; set; } = new PropertyWithAppliesTo<int>("Common");
[XmlIgnore] // this value isn't serialized, but the property here for easy syntax
public int VehicleId
{
get { return VehicleIdMeta.Value; }
set { VehicleIdMeta.Value = value; }
}
}
public class PropertyWithAppliesTo<T>
{
[XmlAttribute] // tells serializer this should be an attribute on this element, not a property
public string AppliesTo { get; set; } = string.Empty;
[XmlText] // tells serializer to not write this as a property, but as the main XML text
public T Value { get; set; } = default;
public PropertyWithAppliesTo() : this(string.Empty) { }
public PropertyWithAppliesTo(string appliesTo) : this(appliesTo, default) { }
public PropertyWithAppliesTo(string appliesTo, T initialValue)
{
AppliesTo = appliesTo;
Value = initialValue;
}
}
}
When run, the string s will look like:
<?xml version=\"1.0\" encoding=\"utf-16\"?>
<Vehicle xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
<VehicleId AppliesTo="Common">1244</VehicleId>
</Vehicle>
Other Notes:
You can see how to add more properties to Vehicle: add a property of type PropertyWithAppliesTo<T> marked with XmlElement to give it the name you want, and then a property of type T marked with XmlIgnore that wraps around the Value you want.
You can control the value of AppliesTo by changing the input to the constructor of PropertyWithAppliesTo<T> and giving it a different metadata string.
If you don't want consumers of your library to see the 'meta' properties in IntelliSense, you can use the EditorBrowsableAttribute. It won't hide things from you when using the source and a project reference; it's only hidden when referencing the compiled dll.
This is admittedly an annoying way to add properties to a class. But if you want to use the default .NET XML serializer, this is a way to achieve the XML you want.

How to deserialize two different XML types in to one class

I have two different XML documents. Their structure is almost identical, but they have some different elements.
I would like to deserialize the incoming documents in to one class which is a superset of both classes. There is no need to ever serialize the class, I only need to deserialize the documents.
The XML document types have a different root element, lets say the root of the first is <CLASSA>and the other is <CLASSB>.
I am looking for something like this, where both <CLASSA> and <CLASSB> xml documents are mapped to ClassAandB:
[XmlRoot(ElementName="CLASSA,CLASSB")]
public class ClassAandB {
[XmlElement(ElementName="syntaxid")]
public Syntaxid Syntaxid{ get; set; }
[XmlElement(ElementName="email")]
public Email Email { get; set; }
[XmlElement(ElementName="envelope")]
public Envelope Envelope { get; set; }
[XmlElement(ElementName="header")]
public Header Header { get; set; }
}
I can then find out which of the two types it is by reading the Syntaxid property. This helps me because a lot of the processing is the same for both types.
Any suggestions how to do this?
Since the xml root element name might depend on the content of the xml document, you'll have to configure the XmlSerializer at runtime with this xml root element name to be used.
In this case, there's no need anymore to apply an XmlRootAttribute.
This can be done via the constructor overload accepting an XmlRootAttribute argument, via which you pass the root element name.
public XmlSerializer (Type type, System.Xml.Serialization.XmlRootAttribute root);
You might know the root element name in front eg. depending on the source of the xml document, or you might discover it at runtime from the xml document itself.
The following from the example below shows how the xml root element name gets set.
String rootName = "CLASSA"; // "CLASSB"
var serializer = new XmlSerializer(typeof(ClassAandB), new XmlRootAttribute(rootName));
An simplified example using an XmlReader as source and retrieving the root xml element name from the content.
public class ClassAandB
{
[XmlElement(ElementName="syntaxid")]
public String Syntaxid{ get; set; }
[XmlElement(ElementName="email")]
public String Email { get; set; }
[XmlElement(ElementName="header")]
public String Header { get; set; }
}
var classA = Deserialize(XmlReader.Create(
new StringReader("<CLASSA><syntaxid>A</syntaxid></CLASSA>"))
);
Console.WriteLine(classA.Syntaxid); // A
var classB = Deserialize(
XmlReader.Create(new StringReader("<CLASSB><syntaxid>B</syntaxid></CLASSB>"))
);
Console.WriteLine(classB.Syntaxid); // B
public static ClassAandB Deserialize(XmlReader reader)
{
reader.MoveToContent();
string rootName = reader.Name;
var serializer = new XmlSerializer(typeof(ClassAandB),
new XmlRootAttribute(rootName)
);
var deserialized = serializer.Deserialize(reader) as ClassAandB;
return deserialized;
}
I suggest you to remove XmlRoot attribute and use:
var doc = new XmlDocument();
doc.Load("file.xml");
XmlElement root = xmlDoc.DocumentElement;
var serializer = new XmlSerializer(typeof(ClassAandB), new XmlRootAttribute(root.ToString()));

Error in xml document (1, 2) ... was not expected

I've tried several of the suggestions found on other SO answers for this same error but nothing has helped. Adding a namespace attr, using a blank namespace attr. Loading the string into an xmlDocument then rewriting it back to an xmlWriter. Using Serialazble instead of DataContract. Using xmlRoot attr with and without a namespace. Nothing works! Help.
I'm trying to instantiate a FillRequestExtended with the constructor that accepts an xml formatted string and always get the error Error in xml document (1, 2)
The Inner Exception reads: "http://schemas.datacontract.org/2004/07/WdRx.Exactus.Objects'> was not expected."
The class looks like so:
namespace WdRx.Exactus.Objects
{
[DataContract]
public class FillRequestExtended : IExtendedData
{
[DataMember]
[XmlElement]
public KeyValuePair<string, string>[] Properties { get; set; }
[DataMember]
[XmlElement]
public List<Payment> PaymentItems { get; set; }
public FillRequestExtended()
{
}
public FillRequestExtended(string xml)
{
FillRequestExtended extendedData;
XmlSerializer xs = new XmlSerializer(typeof(FillRequestExtended));
using (StringReader sr = new StringReader(xml))
{
XmlReader reader = XmlReader.Create(sr);
extendedData = (FillRequestExtended)xs.Deserialize(reader);
}
Properties = extendedData.Properties;
PaymentItems = new List<Payment>(extendedData.PaymentItems);
}
}
}
The string passed in is serialized elsewhere with no issue and looks like so:
<FillRequestExtended xmlns=\"http://schemas.datacontract.org/2004/07/WdRx.Exactus.Objects\"
xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
<PaymentItems>
<Payment>
<Amount>-43.95</Amount>
<Summary>CCP PAYMENT - AUTH:014910</Summary>
</Payment>
<Payment>
<Amount>0.00</Amount>
<Summary>Type: VIS Account: ************5793 Expires: 05/16 Authorization: 014910</Summary>
</Payment>
</PaymentItems>
<Properties xmlns:a=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">
<a:KeyValuePairOfstringstring>
<a:key>RxDcDate</a:key>
<a:value>20150414</a:value>
</a:KeyValuePairOfstringstring>
<a:KeyValuePairOfstringstring>
<a:key>RefillStatus</a:key>
<a:value>PROFILED</a:value>
</a:KeyValuePairOfstringstring>
</Properties>
</FillRequestExtended>
There are at least two different XML deserializers. By decorating the class with [DataContract] you are forcing it to use the WCF serializer. In the deserialize code, you are using the normal XML object deserializer.

Deserialize XML Elements Return NULL

I am trying to deserialize an XML file. However, I only want two elements from the file. Here's the basic markup:
<Stuff>
<Details>
<Comment>I want whats in here.</Comment>
<LogLevel>And here too.</LogLevel>
</Details>
<Stuff>
To deserialize I'm doing the following:
XmlSerializer deserializer;
FileStream stream = new FileStream(CONFIG_PATH, FileMode.Open);
XmlReader reader = new XmlTextReader(stream);
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "Stuff";
xRoot.IsNullable = true;
// Details configuration area.
Utilities.Details d = new Utilities.Details();
deserializer = new XmlSerializer((typeof(Details)), xRoot);
d = (Details)deserializer.Deserialize(reader);
System.Windows.MessageBox.Show(d.Comment);
And finally the class that holds the objects:
/// <summary>
/// Configuration details.
/// </summary>
[Serializable()]
[XmlRoot(ElementName = "Details", IsNullable = true)]
public sealed class Details
{
public Details()
{
}
[XmlElement("Comment")]
public string Comment { get; set; }
[XmlElement("LogLevel")]
public string LogLevel { get; set; }
}
However d.Comment and d.LogLevel continue to return null no matter what I do. Any ideas?
With that setup, it expects
<Stuff>
<Comment>....
<LogLevel>...
...
To handle two levels in the XML you will need an object model that matches. Rather than messing with the XmlRootAttribute at runtime, write a type Stuff that has a Details instance in a property called Details. Then create the serializer to expect a Stuff instance:
public class Stuff {
public Details Details {get;set;}
}
An alternative approach would be to use a sub-reader over the input, but that is harder.
I ran into a lot of similar issues when I was attempting to use XmlSerializer and FileStreams.
I would suggest changing this to Linq to XML. I found it to be a lot easier and faster to learn.
Here is a great Video by Mike Taulty
Linq to XML Tutorial

How do I change root element name while keeping contents using XmlSerializer?

I have an XML document:
<data>
<elmt1>Element 1</elmt1>
<elmnt2>Element 2</elmnt2>
<elmnt3>Element 3</elmnt3>
</data>
I need to deserialize to an object that serializes to a different root name with everything else remaining the same.
For example:
<dataNew>
<elmt1>Element 1</elmt1>
<elmnt2>Element 2</elmnt2>
<elmnt3>Element 3</elmnt3>
</dataNew>
When serializing, we can always apply XmlRootAttribute to serialize to a different root name but I am not sure how to deserialize to a different XmlRootAttribute. It keeps failing error in document (1,2) pointing to the root attribute.
How can I achieve that?
If it's only the root name you want to change you can specify the root attribute when declaring the XmlSerializer.
XmlSerializer xmlSerializer = new XmlSerializer(typeof(data), new XmlRootAttribute("dataNew"));
XmlRootAttribute was supposed to work
[XmlRoot("dataNew")]
public class MyData()
{
[XmlElement("elmt1")]
public string myElement1{get;set;}
[XmlElement("elmnt2")]
public string myElement2{get;set;}
[XmlElement("elmtn3")]
public string myElement3{get;set;}
}
EDIT: Completed the XML
Did you try using the XmlAttributeOverrides class?
a sample of using XmlAttributeOverrides. If you vote up give one to hjb417 as well
class Program
{
static void Main(string[] args)
{
using (var fs = File.OpenRead("XmlFile1.xml"))
using (var fs2 = File.OpenRead("XmlFile2.xml"))
{
var xSer = new XmlSerializer(typeof(data));
var obj = xSer.Deserialize(fs);
//
var xattribs = new XmlAttributes();
var xroot = new XmlRootAttribute("dataNew");
xattribs.XmlRoot = xroot;
var xoverrides = new XmlAttributeOverrides();
xoverrides.Add(typeof(data), xattribs);
var xSer2 = new XmlSerializer(typeof(data), xoverrides);
var obj2 = xSer2.Deserialize(fs2);
}
}
}
public class data
{
public string elmt1 { get; set; }
public string elmnt2 { get; set; }
public string elmnt3 { get; set; }
}
You can use ExtendedXmlSerializer. This serializer support change root element name and property name.
If you have class like this:
[XmlRoot("dataNew")]
public class Data
{
[XmlElement("elmt1")]
public string Element1 { get; set; }
[XmlElement("elmnt2")]
public string Element2 { get; set; }
[XmlElement("elmtn3")]
public string Element3 { get; set; }
}
You can serialize it:
ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var obj = new Data
{
Element1 = "A",
Element2 = "B",
Element3 = "C",
};
var xml = serializer.Serialize(obj);
Your xml will look like:
<?xml version="1.0" encoding="utf-8"?>
<dataNew type="Models.Example">
<elmt1>A</elmt1>
<elmnt2>B</elmnt2>
<elmtn3>C</elmtn3>
</dataNew>
ExtendedXmlSerializer has many other useful features:
Deserialization xml from standard XMLSerializer
Serialization class with property interface
Serialization circular reference and reference Id
Deserialization of old version of xml
Property encryption
Custom serializer
ExtendedXmlSerializer supports .net 4.5 and .net Core. You can integrate it with WebApi and AspCore.
You might have to implement ISerializable and change the root element in GetObjectData().

Categories

Resources