How do I use XmlSerializer to handle different namespace versions? - c#

I am using the .NET XmlSerializer class to deserialize GPX files.
There are two versions of the GPX standard:
<gpx xmlns="http://www.topografix.com/GPX/1/0"> ... </gpx>
<gpx xmlns="http://www.topografix.com/GPX/1/1"> ... </gpx>
Also, some GPX files do not specify a default namespace:
<gpx> ... </gpx>
My code needs to handle all three cases, but I can't work out how to get XmlSerializer to do it.
I am sure there must be a simple solution because this a common scenario, for example KML has the same issue.

I have done something similar to this a few times before, and this might be of use to you if you only have to deal with a small number of namespaces and you know them all beforehand. Create a simple inheritance hierarchy of classes, and add attributes to the different classes for the different namespaces. See the following code sample. If you run this program it gives the output:
Deserialized, type=XmlSerializerExample.GpxV1, data=1
Deserialized, type=XmlSerializerExample.GpxV2, data=2
Deserialized, type=XmlSerializerExample.Gpx, data=3
Here is the code:
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
[XmlRoot("gpx")]
public class Gpx {
[XmlElement("data")] public int Data;
}
[XmlRoot("gpx", Namespace = "http://www.topografix.com/GPX/1/0")]
public class GpxV1 : Gpx {}
[XmlRoot("gpx", Namespace = "http://www.topografix.com/GPX/1/1")]
public class GpxV2 : Gpx {}
internal class Program {
private static void Main() {
var xmlExamples = new[] {
"<gpx xmlns='http://www.topografix.com/GPX/1/0'><data>1</data></gpx>",
"<gpx xmlns='http://www.topografix.com/GPX/1/1'><data>2</data></gpx>",
"<gpx><data>3</data></gpx>",
};
var serializers = new[] {
new XmlSerializer(typeof (Gpx)),
new XmlSerializer(typeof (GpxV1)),
new XmlSerializer(typeof (GpxV2)),
};
foreach (var xml in xmlExamples) {
var textReader = new StringReader(xml);
var xmlReader = XmlReader.Create(textReader);
foreach (var serializer in serializers) {
if (serializer.CanDeserialize(xmlReader)) {
var gpx = (Gpx)serializer.Deserialize(xmlReader);
Console.WriteLine("Deserialized, type={0}, data={1}", gpx.GetType(), gpx.Data);
}
}
}
}
}

Here's the solution I came up with before the other suggestions came through:
var settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreProcessingInstructions = true;
settings.IgnoreWhitespace = true;
using (var reader = XmlReader.Create(filePath, settings))
{
if (reader.IsStartElement("gpx"))
{
string defaultNamespace = reader["xmlns"];
XmlSerializer serializer = new XmlSerializer(typeof(Gpx), defaultNamespace);
gpx = (Gpx)serializer.Deserialize(reader);
}
}
This example accepts any namespace, but you could easily make it filter for a specific list of known namespaces.

Oddly enough you can't solve this nicely. Have a look at the deserialize section in this troubleshooting article. Especially where it states:
Only a few error conditions lead to exceptions during the
deserialization process. The most common ones are:
•The name of the
root element or its namespace did not match the expected name.
...
The workaround I use for this is to set the first namespace, try/catch the deserialize operation and if it fails because of the namespace I try it with the next one. Only if all namespace options fail do I throw the error.
From a really strict point of view you can argue that this behavior is correct since the type you deserialize to should represent a specific schema/namespace and then it doesn't make sense that it should also be able to read data from another schema/namespace. In practice this is utterly annoying though. File extenstion rarely change when versions change so the only way to tell if a .gpx file is v0 or v1 is to read the xml contents but the xmldeserializer won't unless you tell upfront which version it will be.

Related

Protobuf-net serialization error: Dynamic type is not a contract-type: Int32

I need to do serialization and deserialization using below code. But always i am getting "Dynamic type is not a contract-type" error while serializing. I am stuck with this. I need to achieve this someway Can somebody help me on this?
using ProtoBuf;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
namespace ProtoSerialization
{
class Program
{
static void Main(string[] args)
{
try
{
byte[] arr;
ClassA obj = new ClassA();
obj.ColumnList = new List<int> {1 };
using (var stream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(stream, obj);
arr = stream.ToArray();
}
using(var stream=new MemoryStream(arr))
{
var result = Serializer.Deserialize(typeof(ClassA), stream);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadKey();
}
}
}
[ProtoContract]
public class ClassA
{
[ProtoMember(1,DynamicType =true)]
public IList ColumnList;
}
}
I strongly advise against using the dynamic feature, here or elsewhere. It is deprecated in V3, probably permanently. It looks like what you really want here is something like a Value from struct.proto. This doesn't have inbuilt support in protobuf-net currently, but I'll probably add it soon. As for now: I'd probably advise using an array of a type that has those things as fields. If you make the layout look like Value does, in terms of the field numbers, it should be directly swappable later.
Mostly copied from question about protobuf dynamic array.
The documentation for dynamic arrays state:
DynamicType - stores additional Type information with the type (by default it includes the AssemblyQualifiedName, although this can be controlled by the user). This makes it possible to serialize weak models, i.e. where object is used for property members, however currently this is limited to contract types (not primitives), and does not work for types with inheritance (these limitations may be removed at a later time). Like with AsReference, this uses a very different layout format
You have a primitive, therefore you can not use DynamicType. As explained in the error message you got.
A workaround could be to wrap the primitives you want to store in another type with a defined contract.

How to pipe custom types in a c# console app (powershell like)?

I Know I'm able to pipe out/in using simple Console.WriteLine and Console.ReadLine methods, but that way I'm passing a string between processes (which must be parsed to recreate the object).
What I'm wondering is if I would be able to pipe my own types, so that I could retrieve them easily in destiny process. What I expect is to do something like:
myProgram | get-member
And the output would something like MyNameSpace.MyType and the list of its members (currently it shows the typeName System.String)
Is that possible in a console app or could I only achieve this using cmdlets?
The easiest way to do this is to use serialization to turn the objects you wish to send from one to the other into a pipeable format to send them from one to the other. There are, however, a number of constraints on doing this:
First, the implementation of the types you're passing back and forth have to be available to all the apps that may handle them. (That's not a problem for PowerShell because all the cmdlets run inside the same process.) So the easiest way to do this is to create the types you're going to pipe around inside a class library that's referenced by all the console apps. This class, for example, I put in my sample shared library:
[Serializable]
public class TestClass
{
public string Test { get; set; }
public string TestAgain { get; set; }
public string Cheese { get; set; }
}
The [Serializable] attribute marks it as serializable, which is sufficient for simple classes. For more complex classes, more may be required - see MSDN, starting here: http://msdn.microsoft.com/en-us/library/4abbf6k0(v=VS.71).aspx
Then, in the program you're piping from, you serialize it to XML and write it out to console like this:
using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Shared;
namespace Out
{
class Program
{
static void Main(string[] args)
{
// Create the object.
TestClass test = new TestClass();
test.Test = "Monkey";
test.TestAgain = "Hat";
test.Cheese = "Fish";
// Serialize it.
XmlSerializer serializer = new XmlSerializer(typeof (TestClass));
StringBuilder sb = new StringBuilder();
using (var writer = new StringWriter(sb))
serializer.Serialize(writer, test);
// And write it to console.
Console.WriteLine(sb.ToString());
}
}
}
When run, this outputs the instance's properties encoded in XML, thus:
<?xml version="1.0" encoding="utf-16"?>
<TestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http
://www.w3.org/2001/XMLSchema">
<Test>Monkey</Test>
<TestAgain>Hat</TestAgain>
<Cheese>Fish</Cheese>
</TestClass>
Then, in your receiving application, you reverse the process, reading from the console, thus:
using System;
using System.IO;
using System.Xml.Serialization;
using Shared;
namespace In
{
class Program
{
static void Main(string[] args)
{
// Read the input XML; until complete.
string input = Console.In.ReadToEnd();
TestClass passedIn;
// Deserialize it.
var serializer = new XmlSerializer(typeof (TestClass));
using (var reader = new StringReader(input))
passedIn = (TestClass) serializer.Deserialize(reader);
// Do something with the object.
Console.WriteLine("Test: {0}", passedIn.Test);
Console.WriteLine("TestAgain: {0}", passedIn.TestAgain);
Console.WriteLine("Cheese: {0}", passedIn.Cheese);
}
}
}
And voila!
C:\Working\PipeExample\In\bin\Debug>..\..\..\Out\bin\Debug\Out.exe | in
Test: Monkey
TestAgain: Hat
Cheese: Fish
You'll need some additional code, of course, to make sure that the receiving application knows what type(s) to expect - or can handle anything it gets - and since the intermediate XML is not very human-parsable, you'll need a way to make sure that the sending application knows when it's talking to a pipe and when it's talking to a human. In .NET 4.5, the Console.IsOutputRedirected() method will do that for you ( http://msdn.microsoft.com/en-us/library/system.console.isoutputredirected%28v=VS.110%29.aspx ), but in earlier versions, there's not an easy way to get at this information programmatically.
But this is the core of the thing, and looking at the documentation for and around XmlSerializer should give you the rest.
Why don't you write your own cmdlet instead of a console program?
A PowerShell module can be a binary module (a DLL assembly) composed by cmdlets writen in C#. Have a look to Installing the Windows PowerShell SDK.

C# Web Service XmlSerializer issue

I have a C# Web Service that is serializing my simple class:
[Serializable]
[XmlInclude(typeof(Bitmap))]
[XmlTypeAttribute(Namespace = "http://tempuri.org/")]
public class Class1
{
private static Bitmap _myImage = new Bitmap(#"C:\WebApplication1\ClassLibrary1\Untitled.png");
public Bitmap MyImage
{
get { return _myImage; }
set
{
_myImage = value;
}
}
}
Here's the asmx.cs code that does the serialization:
[WebMethod]
public string HelloWorld()
{
var c = new Class1();
XmlSerializer serializer = new XmlSerializer(typeof(Class1));
return XMLSerializer(c);
}
public string XMLSerializer(object pObject)
{
try
{
XmlSerializer xs = new XmlSerializer(pObject.GetType());
using (StringWriter stream = new StringWriter())
{
xs.Serialize(stream, pObject);
stream.Flush();
return stream.ToString();
}
}
catch (Exception ex)
{
return ex.ToString();
}
}
Looks prety straight forward. However, the XML generated by the XmlSerializer is producing and error when I try to DeSerialize it.
{"There is an error in XML document (5, 5)."}
{"Parameter is not valid."}
When I try to load the generated XML into IE I get this error.
Switch from current encoding to specified encoding not supported. Error processing resource 'file:///C:/Users/mhall15/Deskt...
<?xml version="1.0" encoding="utf-16"?>
Here's the generated XML:
<?xml version="1.0" encoding="utf-16"?>
<Class1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyImage xmlns="http://tempuri.org/">
<Palette />
</MyImage>
</Class1>
Any ideas what's going on?
During the serialization, replace "encoding="utf-16" with "encoding="utf-8"" and that will cut it. The source of the problem - I'm not sure, but I've ran into it numerous times, and that's how I dealt with it.
That's how to deal with the IE issue.
The deserialization should be amongst these lines. I'm posting the kind of arbitrary code I normally use:
public static object DeserializeFromXML<T>(string _input)
{
object _temp = Activator.CreateInstance<T>();
Type expected_type = _temp.GetType();
_temp = null;
XmlSerializer serializer = new XmlSerializer(expected_type);
StringReader stringWriter = new StringReader(_input);
object _copy = serializer.Deserialize(stringWriter);
return _copy;
}
In the above example, I'm using templating for reusability sake. You should be able to call the method by saying DeserializeFromXML < Class1 >(_xml_input) where xml input is the string. That will force the compiler to use the definition of the given class to deserialize the XML input. That way you wouldn't even have to change the encoding in the serialization. If it's also a case where you may or may not know the data type to deserialize with, you can use a strategy design pattern where you register the root type of the XML with it's associated generic type. Later on you can reference that registry and arbitrarily deserialize any XML input as long as the root type is registered. It's a trick i normally use as well. If more is needed on this, let me know, and I'll explain in detail.
In addition,if you are running IE 9, the new update to IE 9 makes it difficult to view XML. Press F12 - go to developer tools and change your browser mode to run as IE 8 instead of IE 9.

How can I serialize internal classes using XmlSerializer?

I'm building a library to interface with a third party. Communication is through XML and HTTP Posts. That's working.
But, whatever code uses the library does not need to be aware of the internal classes. My internal objects are serialized to XML using this method:
internal static string SerializeXML(Object obj)
{
XmlSerializer serializer = new XmlSerializer(obj.GetType(), "some.domain");
//settings
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
using (StringWriter stream = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(stream, settings))
{
serializer.Serialize(writer, obj);
}
return stream.ToString();
}
}
However, when I change my classes' access modifier to internal, I get an exception at runtime:
[System.InvalidOperationException] = {"MyNamespace.MyClass is inaccessible due to its protection level. Only public types can be processed."}
That exception happens in the first line of the code above.
I would like my library's classes not to be public because I do not want to expose them. Can I do that? How can I make internal types serializable, using my generic serializer? What am I doing wrong?
From Sowmy Srinivasan's Blog - Serializing internal types using XmlSerializer:
Being able to serialize internal types is one of the common requests
seen by the XmlSerializer team. It is a reasonable request from people
shipping libraries. They do not want to make the XmlSerializer types
public just for the sake of the serializer. I recently moved from the
team that wrote the XmlSerializer to a team that consumes
XmlSerializer. When I came across a similar request I said, "No way.
Use DataContractSerializer".
The reason is simple. XmlSerializer works by generating code. The
generated code lives in a dynamically generated assembly and needs to
access the types being serialized. Since XmlSerializer was developed
in a time before the advent of lightweight code generation, the
generated code cannot access anything other than public types in
another assembly. Hence the types being serialized has to be public.
I hear astute readers whisper "It does not have to be public if
'InternalsVisibleTo' attribute is used".
I say, "Right, but the name of the generated assembly is not known
upfront. To which assembly do you make the internals visible to?"
Astute readers : "the assembly name is known if one uses 'sgen.exe'"
Me: "For sgen to generate serializer for your types, they have to be
public"
Astute readers : "We could do a two pass compilation. One pass for
sgen with types as public and another pass for shipping with types as
internals."
They may be right! If I ask the astute readers to write me a sample
they would probably write something like this. (Disclaimer: This is
not the official solution. YMMV)
using System;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.CompilerServices;
using System.Reflection;
[assembly: InternalsVisibleTo("Program.XmlSerializers")]
namespace InternalTypesInXmlSerializer
{
class Program
{
static void Main(string[] args)
{
Address address = new Address();
address.Street = "One Microsoft Way";
address.City = "Redmond";
address.Zip = 98053;
Order order = new Order();
order.BillTo = address;
order.ShipTo = address;
XmlSerializer xmlSerializer = GetSerializer(typeof(Order));
xmlSerializer.Serialize(Console.Out, order);
}
static XmlSerializer GetSerializer(Type type)
{
#if Pass1
return new XmlSerializer(type);
#else
Assembly serializersDll = Assembly.Load("Program.XmlSerializers");
Type xmlSerializerFactoryType = serializersDll.GetType("Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializerContract");
MethodInfo getSerializerMethod = xmlSerializerFactoryType.GetMethod("GetSerializer", BindingFlags.Public | BindingFlags.Instance);
return (XmlSerializer)getSerializerMethod.Invoke(Activator.CreateInstance(xmlSerializerFactoryType), new object[] { type });
#endif
}
}
#if Pass1
public class Address
#else
internal class Address
#endif
{
public string Street;
public string City;
public int Zip;
}
#if Pass1
public class Order
#else
internal class Order
#endif
{
public Address ShipTo;
public Address BillTo;
}
}
Some astute 'hacking' readers may go as far as giving me the build.cmd
to compile it.
csc /d:Pass1 program.cs
sgen program.exe
csc program.cs
As an alternative you can use dynamically created public classes (which won't be exposed to the 3rd party):
static void Main()
{
var emailType = CreateEmailType();
dynamic email = Activator.CreateInstance(emailType);
email.From = "x#xpto.com";
email.To = "y#acme.com";
email.Subject = "Dynamic Type";
email.Boby = "XmlSerializer can use this!";
}
static Type CreateEmailType()
{
var assemblyName = new AssemblyName("DynamicAssembly");
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
var typeBuilder = moduleBuilder.DefineType(
"Email",
(
TypeAttributes.Public |
TypeAttributes.Sealed |
TypeAttributes.SequentialLayout |
TypeAttributes.Serializable
),
typeof(ValueType)
);
typeBuilder.DefineField("From", typeof(string), FieldAttributes.Public);
typeBuilder.DefineField("To", typeof(string), FieldAttributes.Public);
typeBuilder.DefineField("Subject", typeof(string), FieldAttributes.Public);
typeBuilder.DefineField("Body", typeof(string), FieldAttributes.Public);
return typeBuilder.CreateType();
}
The no headache solution is to use the NetBike.Xml NuGet package (.NET Standard 2.0) which is compatible with System.Xml.XmlSerializer XML attributes and support internal types out of the box.
Usage is straightforward, here's an excerpt from the README:
var serializer = new XmlSerializer();
var xml = "<Foo><Id>1</Id><Name>test</Name></Foo>";
var foo = serializer.Deserialize<Foo>(new StringReader(xml));
This may help you: MRB_ObjectSaver
This project helps you save/load/clone any object in c# to/from a file/string. In compare to "c# serialization" this method keeps reference to objects and link between objects will not break. (see: SerializeObjectTest.cs for an example) Furthermore, the type have not be marked as [Serializable]
You can also use something called xgenplus (http://xgenplus.codeplex.com/) this generates the code that would normally get executed at runtime. Then you add that to your solution, and compile it as part of your solution. At that point, it doesn't matter if your object is internal - you can add the pre-generated code in the same namespace. The performance for this is blazing fast, as its all pre-generated.

HR-XML Error, trying to deserialize XML examples

The HR-XML 3.0 spec provides WSDL's to generate their entities. I'm trying to deserialize their example xml in their documentation, but it's not working.
Candidate.CandidateType candidate = null;
string path = "c:\\candidate.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Candidate.CandidateType), "http://www.hr-xml.org/3");
StreamReader reader = null;
reader = new StreamReader(path);
candidate = (Candidate.CandidateType)serializer.Deserialize(reader);
The error I'm getting:
"<Candidate xmlns='http://www.hr-xml.org/3'> was not expected."
Any suggestions?
Update: I tried XmlSerializing a CandidatePerson element and it looks like it uses CandidatePersonType instead of CandidatePerson. I think I'm doing something wrong here though...
first lines of Candidate.CandidateType (all auto-generated):
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.3082")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.hr-xml.org/3")]
public partial class CandidateType {
private IdentifierType2 documentIDField;
private IdentifierType2[] alternateDocumentIDField;
private string documentSequenceField;
The following is more of a comment, but it's too long, so I'll put it here.
The CandidateType class is properly decorated with the XmlType attribute. That is an attribute that applies to types, and determines how the type will be emitted in any generated XML Schema. It has nothing to do with the namespace on an element that happens to have the same type.
Consider the following C# code:
public class CandidateType {}
public class Foo
{
CandidateType _candidate1;
CandidateType _candidate2;
}
Note that you can have multiple variables of the same type. In the same way, you could have:
<xs:element name="Candidate1" type="hrx:CandidateType"/>
<xs:element name="Candidate2" type="hrx:CandidateType"/>
These are two elements which will validate against the same type definition, but which are otherwise unrelated. If they are in the same XML Schema, then they will be in the same namespace. But what if they're not? Then you could have an instance document like:
<ns1:Candidate1 xmlns:ns1="namespace1" xmlns="http://www.hr-xml.org/3"> ... </ns1:Candidate1>
<ns2:Candidate2 xmlns:ns2="namespace2" xmlns="http://www.hr-xml.org/3"> ... </ns1:Candidate2>
What you need to do is specify the namespace of the Candidate element to the XML Serializer. The fact that the CandidateType type is in a particular namespace does not determine the namespace of the Candidate element.
Muahaha I figured it out finally!
John Saunders was right, I needed to specify the default namespace in the XmlSerializer, but in addition to that I have to specify the XmlRootAttribute because the Class that I'm trying to de-serialize to does not have the same name as the root element.
Here is my code for de-serializing the HR-XML ProcessCandidate example:
protected static ImportTest.CandidateService.ProcessCandidateType DeserializeProcessCandidate(string path)
{
CandidateService.ProcessCandidateType processCandidate = null;
XmlRootAttribute root = new XmlRootAttribute("ProcessCandidate");
XmlSerializer serializer = new XmlSerializer(typeof(CandidateService.ProcessCandidateType), new XmlAttributeOverrides(), new Type[0], root, "http://www.hr-xml.org/3");
StreamReader reader = null;
try
{
reader = new StreamReader(path);
processCandidate = (CandidateService.ProcessCandidateType)serializer.Deserialize(reader);
reader.Close();
}
catch (Exception ex)
{
reader.Close();
throw (new Exception(ex.InnerException.Message));
}
return processCandidate;
}
Thanks for the help John!

Categories

Resources