This is a sample of my xml file:
<IFX xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="finalizacaoOrcamentoVO">
<dadosOrcamento>...</dadosOrcamento>
<faturamento>...</faturamento>
</IFX>
This is my auto-generated by Visual Studio object class:
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class IFX
{
private IFXDadosOrcamento dadosOrcamentoField;
private IFXFaturamento faturamentoField;
But I've been getting this error every time I try to deserialize:
Message "Error document XML (1, 57)." string
This is my deserialize method:
IFX document;
XmlSerializer serializer = new XmlSerializer(typeof(object));
using (var reader = XmlReader.Create(file.InputStream))
{
document = (IFX)serializer.Deserialize(reader);
}
Any hint on what should be fixed?
Thanks in advance!
These are my subclasses:
ClassObject
The xsi:type attribute, short for {http://www.w3.org/2001/XMLSchema-instance}type, is a w3c standard attribute that is used to explicitly assert the type of its element. As explained in Xsi:type Attribute Binding Support, XmlSerializer interprets this to mean that the element is serialized from a polymorphic derived type of the expected type.
I.e. if your XML looks like:
<IFX xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="finalizacaoOrcamentoVO">
<dadosOrcamento>
<IFXDadosOrcamentoValue>A IFXDadosOrcamentoValue</IFXDadosOrcamentoValue>
</dadosOrcamento>
</IFX>
Then XmlSerializer expects that the following classes will exist:
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
[XmlInclude(typeof(finalizacaoOrcamentoVO))]
public partial class IFX
{
private IFXDadosOrcamento dadosOrcamentoField;
public IFXDadosOrcamento dadosOrcamento { get { return dadosOrcamentoField; } set { dadosOrcamentoField = value; } }
}
public class finalizacaoOrcamentoVO : IFX
{
// This derived type may have some or all of the properties shown as elements in the XML file.
}
public class IFXDadosOrcamento
{
public string IFXDadosOrcamentoValue { get; set; }
}
Where finalizacaoOrcamentoVO is a type that inherits from IFX.
Note the presence of [XmlInclude(typeof(finalizacaoOrcamentoVO))]. This attribute informs the serializer of the subtypes that might be encountered and must be present for every allowed subtype.
Having done this, the XML can now be deserialized via:
IFX document;
XmlSerializer serializer = new XmlSerializer(typeof(IFX));
using (var reader = XmlReader.Create(inputStream))
{
document = (IFX)serializer.Deserialize(reader);
}
An instance of finalizacaoOrcamentoVO will actually get created thereby.
That being said, in your comment you state that your auto-generated classes do not include a derived type finalizacaoOrcamentoVO. If you generated the classes from an XSD, then the XSD and XML do not match and you should get that resolved. If you generated the classes by pasting this very XML into Visual Studio, then there might be a bug or limitation in Visual Studio's code generation, which can be fixed manually as shown above.
If you really want to ignore the xsi:type attribute, then you will have to do so manually, since support for it is built-in to the serializer. One way is to load into an intermediate XDocument:
XDocument xDoc;
using (var reader = XmlReader.Create(inputStream))
{
xDoc = XDocument.Load(reader);
}
var attr = xDoc.Root.Attribute("{http://www.w3.org/2001/XMLSchema-instance}type");
if (attr != null)
attr.Remove();
var document = xDoc.Deserialize<IFX>();
Using the extension method:
public static class XObjectExtensions
{
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer = null)
{
if (element == null)
throw new ArgumentNullException();
using (var reader = element.CreateReader())
return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
}
}
Related
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.
I have the following given:
1) A XML Schema, XSD-file, compiled to C# classes using the XSD.EXE tool.
2) A RabbitMQ message queue containing well formed messages in XML of any type defined in the XML Schema. Here are two snippets of different messages:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<UserReport xmlns=".../v5.1"; ... >
... User report message content...
</UserReport>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CaptureReport xmlns=".../v5.1"; ...>
... Capture report message content...
</CaptureReport>
3) Experience using the XmlSerializer .Net class to deserialize, when the type is known.
The question is how to deserialize messages from XML to a an object, when the type is unknown. It's not possible to instantiate the XmlSerializer, because the type is unknown.
One way is to loop through all possible types until deserialiation succeed, which is a bad solution because there are many different types defined in the XML Schema.
Is there any other alternatives?
There are a few approaches you can take depending on how exactly you've achieved your polymorphism in the XML itself.
Element name is the type name (reflection approach)
You could get the root element name like this:
string rootElement = null;
using (XmlReader reader = XmlReader.Create(xmlFileName))
{
while (reader.Read())
{
// We won't have to read much of the file to find the root element as it will be the first one found
if (reader.NodeType == XmlNodeType.Element)
{
rootElement = reader.Name;
break;
}
}
}
Then you could find the type by reflection like this (adjust reflection as necessary if your classes are in a different assembly):
var serializableType = Type.GetType("MyApp." + rootElement);
var serializer = new XmlSerializer(serializableType);
You would be advised to cache the mapping from the element name to the XML serializer if performance is important.
Element name maps to the type name
If the XML element names are different from the type names, or you don't want to do reflection, you could instead create a Dictionary mapping from the element names in the XML to the XmlSerializer objects, but still look-up the root element name using the snippet above.
Common root element with polymorphism through xsi:type
If your XML messages all have the same root element name, and the polymorphism is achieved by having types identified using xsi:type, then you can do something like this:
using System;
using System.Xml.Serialization;
namespace XmlTest
{
public abstract class RootElement
{
}
public class TypeA : RootElement
{
public string AData
{
get;
set;
}
}
public class TypeB : RootElement
{
public int BData
{
get;
set;
}
}
class Program
{
static void Main(string[] args)
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(RootElement),
new Type[]
{
typeof(TypeA),
typeof(TypeB)
});
RootElement rootElement = null;
string axml = "<RootElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"TypeA\"><AData>Hello A</AData></RootElement>";
string bxml = "<RootElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"TypeB\"><BData>1234</BData></RootElement>";
foreach (var s in new string[] { axml, bxml })
{
using (var reader = new System.IO.StringReader(s))
{
rootElement = (RootElement)serializer.Deserialize(reader);
}
TypeA a = rootElement as TypeA;
if (a != null)
{
Console.WriteLine("TypeA: {0}", a.AData);
}
else
{
TypeB b = rootElement as TypeB;
if (b != null)
{
Console.WriteLine("TypeB: {0}", b.BData);
}
else
{
Console.Error.WriteLine("Unexpected type.");
}
}
}
}
}
}
Note the second parameter to the XmlSerializer constructor which is an array of additional types that you want the .NET serializer to know about.
This is an answer based on #softwariness one, but it provides some automation.
If your classes are generated via xsd then all root types are decorated with XmlRootAttribute so we can use it:
public class XmlVariantFactory
{
private readonly Dictionary<string, Type> _xmlRoots;
public XmlVariantFactory() : this(Assembly.GetExecutingAssembly().GetTypes())
{
}
public XmlVariantFactory(IEnumerable<Type> types)
{
_xmlRoots = types
.Select(t => new {t, t.GetCustomAttribute<XmlRootAttribute>()?.ElementName})
.Where(x => !string.IsNullOrEmpty(x.ElementName))
.ToDictionary(x => x.ElementName, x => x.t);
}
public Type GetSerializationType(XmlReader reader)
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
return _xmlRoots[reader.LocalName];
}
}
throw new ArgumentException("No known root type found for passed XML");
}
}
It scans all type in executing assembly and finds all possible XML roots. You can do it for all assemblies:
public XmlVariantFactory() : this(AppDomain.CurrentDomain.SelectMany(a => a.GetTypes())
{
}
And then you juse use it:
var input = new StringReader(TestResource.firstRequestResponse);
var serializationType = new XmlVariantFactory().GetSerializationType(XmlReader.Create(input));
var xmlReader = XmlReader.Create(input);
bool canDeserialize = new XmlSerializer(serializationType).CanDeserialize(xmlReader);
Assert.True(canDeserialize);
The responsible for the XML Schema have added the xml-tag to a content field in the RabbitMQ protocol header. The header holds the tag for the dto, data transfer object, sent and serialized to xml. This means that a IOC container becomes handy. I have coded a dto builder interface and its implementation by a generic builder. Thus the builder will build a dto when the dto class is specified for generic part. Note that the dto-class is generated by the xsd-tool. In a IOC container like MS Unity I registered the builder interface implementations for dto all classes and added the xml-tag to the register call. The IOC container’s resolver function is called with the actual received xml-tag from the RabbitMQ header in order to instantiate the specific builder of the dto.
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.
I am currently working on a project that requires me to output XML from its endpoints along with JSON. I have the following model:
[DataContract(Namespace="http://www.yale.edu/tp/cas")]
[XmlType("serviceResponse")]
[XmlRoot(Namespace="http://www.yale.edu/tp/cas")]
public class ServiceResponse
{
[XmlElement("authenticationSuccess")]
public AuthenticationSuccess Success { get; set; }
[XmlElement("authenticationFailure")]
public AuthenticationFailure Failure { get; set; }
}
The output is as follows when success is not null:
<serviceResponse>
<authenticationSuccess />
</serviceResponse>
Now, I can see that obviously, I don't have a prefix assigned to the namespace I told the elements to be a part of. My issue is that I cannot find a place to add the namespace prefixes in MVC4 using the media formatter. I have the following in my global.asax:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
GlobalConfiguration.Configuration.Formatters.XmlFormatter.RemoveSerializer(typeof(Models.ServiceResponse));
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializer(typeof(Models.ServiceResponse), new Infrastructure.NamespaceXmlSerializer(typeof(Models.ServiceResponse)));
I made a custom serializer based on XmlSerializer in an attempt to intercept the writing request and tack the namespace list on there. The issue with this method is that right now I have breakpoints inside every overrideable method and none of them are tripped when serializing which leads me to believe that my serializer isn't being used.
Is there some built in way to accomplish what I want to do or am I stuck re-implementing the XmlMediaTypeFormatter to pass in namespaces when it serializes objects?
As a followup answer: The easiest solution for me was to write my own XmlMediaTypeFormatter. As it turns out, its not nearly as intimidating as I thought.
public class NamespacedXmlMediaTypeFormatter : XmlMediaTypeFormatter
{
const string xmlType = "application/xml";
const string xmlType2 = "text/xml";
public XmlSerializerNamespaces Namespaces { get; private set; }
Dictionary<Type, XmlSerializer> Serializers { get; set; }
public NamespacedXmlMediaTypeFormatter()
: base()
{
this.Namespaces = new XmlSerializerNamespaces();
this.Serializers = new Dictionary<Type, XmlSerializer>();
}
public override Task WriteToStreamAsync(Type type, object value, System.IO.Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
lock (this.Serializers)
{
if (!Serializers.ContainsKey(type))
{
var serializer = new XmlSerializer(type);
//we add a new serializer for this type
this.Serializers.Add(type, serializer);
}
}
return Task.Factory.StartNew(() =>
{
XmlSerializer serializer;
lock (this.Serializers)
{
serializer = Serializers[type];
}
var xmlWriter = new XmlTextWriter(writeStream, Encoding.UTF8);
xmlWriter.Namespaces = true;
serializer.Serialize(xmlWriter, value, this.Namespaces);
});
}
}
Here is the formatter as a gist: https://gist.github.com/kcuzner/eef239003d4f99dfacea
The formatter works by exposing the XmlSerializerNamespaces that the XmlSerializer is going to use. This way I can add arbitrary namespaces as needed.
My top model looks as follows:
[XmlRoot("serviceResponse", Namespace="http://www.yale.edu/tp/cas")]
public class ServiceResponse
{
[XmlElement("authenticationSuccess")]
public CASAuthenticationSuccess Success { get; set; }
[XmlElement("authenticationFailure")]
public CASAuthenticationFailure Failure { get; set; }
}
In my Global.asax I added the following to place my formatter on the top of the list:
var xmlFormatter = new Infrastructure.NamespacedXmlMediaTypeFormatter();
xmlFormatter.Namespaces.Add("cas", "http://www.yale.edu/tp/cas");
GlobalConfiguration.Configuration.Formatters.Insert(0, xmlFormatter);
After adding the formatter and making sure my attributes were set up properly, my XML was properly namespaced.
In my case, I needed to add the cas namespace linking to http://www.yale.edu/tp/cas. For others using this, just change/replicate the Add call to your heart's content adding namespaces.
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