I serialized a class to XML. But deserialization to the same class type is failing when schema validation is enabled.
Here is what I'm doing:
creating an object from the serializable class
Serializing that object to XML
Gets the schema from the that object
Adds that schema to validation
deserialize with out validation
deserialize with XMLschema validation
In step six, it is failing...
Here in this code sample, method with validation is failing:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Deserialize
{
public class Program
{
static string filepath = "TestSerilize.xml";
private static object oSchema;
private static XmlReaderSettings oXmlReaderSettings;
static void Main(string[] args)
{
MyObject oMyobject = new MyObject();
oMyobject.MyObjectType = "MyCustomType";
List<Items> olistItems = new List<Items>();
Items oItems = new Items();
oItems.key = "test123";
oItems.value = "testvalue";
olistItems.Add(oItems);
oMyobject.Items = olistItems;
Saveobjecttofile(oMyobject, filepath);
dynamic objDeserialized = null;
objDeserialized = GetObjFormfileWithoutValidation(filepath, oMyobject.GetType());
objDeserialized = GetObjFormfileWithValidation(filepath, oMyobject.GetType());
}
private static dynamic GetObjFormfileWithValidation(string filepath, Type type)
{
XmlReaderSettings oXmlReaderSettings = new XmlReaderSettings();
oXmlReaderSettings.ValidationType = ValidationType.Schema;
dynamic oSchema = GetSchemaFromType(type);
oXmlReaderSettings.Schemas.Add(oSchema);
XmlReader oXmlReader = null;
if (oSchema != null)
{
oXmlReader = XmlReader.Create(filepath, oXmlReaderSettings);
}
else
{
oXmlReader = XmlReader.Create(filepath);
}
object obj = null;
try
{
XmlSerializer oXmlSerializer = new XmlSerializer(type);
obj = oXmlSerializer.Deserialize(oXmlReader);
}
finally
{
oXmlReader.Close();
}
return obj;
}
private static XmlSchema GetSchemaFromType(Type type)
{
var oSoapReflectionImporter = new SoapReflectionImporter();
var oXmlTypeMapping = oSoapReflectionImporter.ImportTypeMapping(type);
var oXmlSchemas = new XmlSchemas();
var oXmlSchema = new XmlSchema();
oXmlSchemas.Add(oXmlSchema);
var oXMLSchemaExporter = new XmlSchemaExporter(oXmlSchemas);
oXMLSchemaExporter.ExportTypeMapping(oXmlTypeMapping);
return oXmlSchema;
}
private static dynamic GetObjFormfileWithoutValidation(string filepath, Type type)
{
XmlReader oXmlReader = null;
oXmlReader = XmlReader.Create(filepath);
object obj = null;
try
{
XmlSerializer oXmlSerializer = new XmlSerializer(type);
obj = oXmlSerializer.Deserialize(oXmlReader);
}
finally
{
oXmlReader.Close();
}
return obj;
}
private static void Saveobjecttofile(object objectToSave, string filepath)
{
try
{
System.Xml.Serialization.XmlSerializer oXmlSerializer = new System.Xml.Serialization.XmlSerializer(objectToSave.GetType());
using (System.Xml.XmlTextWriter oXmlTextWriter = new System.Xml.XmlTextWriter(filepath, System.Text.Encoding.UTF8))
{
oXmlTextWriter.Indentation = 2;
oXmlTextWriter.Formatting = System.Xml.Formatting.Indented;
oXmlSerializer.Serialize(oXmlTextWriter, objectToSave);
oXmlTextWriter.Flush();
oXmlTextWriter.Close();
}
}
catch (Exception)
{ throw; }
}
}
[XmlType("Items")]
public class Items
{
[XmlAttribute("key")]
public string key { get; set; }
[XmlText()]
public string value { get; set; }
}
[Serializable, XmlRoot("MyObject")]
public class MyObject
{
[XmlElement("MyObjectType", IsNullable = true)]
public string MyObjectType { get; set; }
[XmlElement("Items")]
public List<Items> Items;
public string this[string key]
{
get
{
return null != Items.Find(x => x.key == key) ? Items.Find(x => x.key == key).value : null;
}
set
{
if (Items == null) Items = new List<Items>();
if (null != Items.Find(x => x.key == key))
{
Items.Find(x => x.key == key).value = value;
}
else
{
Items.Add(new Items { key = key, value = value });
}
}
}
}
}
Exception details:
System.Xml.Schema.XmlSchemaException
Message:There is an error in XML document (3, 10).
Inner Exception message:The 'key' attribute is not declared.
StackTrace:
at system.Xml.Schema.XmlSchemaValidator.SendValidationEvent(XmlSchemaValidationException e, XmlSeverityType severity)
at System.Xml.Schema.XmlSchemaValidator.SendValidationEvent(String code, String arg)
at System.Xml.Schema.XmlSchemaValidator.ValidateAttribute(String lName, String ns, XmlValueGetter attributeValueGetter, String attributeStringValue, XmlSchemaInfo schemaInfo)
at System.Xml.Schema.XmlSchemaValidator.ValidateAttribute(String localName, String namespaceUri, XmlValueGetter attributeValue, XmlSchemaInfo schemaInfo)
at System.Xml.XsdValidatingReader.ValidateAttributes()
at System.Xml.XsdValidatingReader.ProcessElementEvent()
at System.Xml.XsdValidatingReader.ProcessReaderEvent()
at System.Xml.XsdValidatingReader.Read()
at System.Xml.XmlReader.MoveToContent()
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderMyObject.Read3_MyObject(Boolean isNullable, Boolean checkType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderMyObject.Read4_MyObject()
Demo fiddle here.
Your problem is here:
private static XmlSchema GetSchemaFromType(Type type)
{
var oSoapReflectionImporter = new SoapReflectionImporter();
SoapReflectionImporter is designed to generate a schema for a c# type that has been marked with SOAP attributes. Such a schema can be used to generate an XmlSerializer customized to use such attributes, as shown in How to: Serialize an Object as a SOAP-Encoded XML Stream:
XmlTypeMapping myTypeMapping = new SoapReflectionImporter().ImportTypeMapping(type);
XmlSerializer mySerializer = new XmlSerializer(myTypeMapping);
You, however, are not using SOAP attributes. You are using regular XmlSerializer attributes, as can be see e.g. in your Items class:
[XmlType("Items")]
public class Items
{
[XmlAttribute("key")]
public string key { get; set; }
[XmlText()]
public string value { get; set; }
}
Thus you should be using XmlReflectionImporter instead:
private static XmlSchema GetSchemaFromType(Type type)
{
var oReflectionImporter = new XmlReflectionImporter();
var oXmlTypeMapping = oReflectionImporter.ImportTypeMapping(type);
var oXmlSchemas = new XmlSchemas();
var oXmlSchema = new XmlSchema();
oXmlSchemas.Add(oXmlSchema);
var oXMLSchemaExporter = new XmlSchemaExporter(oXmlSchemas);
oXMLSchemaExporter.ExportTypeMapping(oXmlTypeMapping);
return oXmlSchema;
}
Related: How do I programmatically generate an xml schema from a type?
Fixed sample demo here.
Related
i'm trying to load and save my data in my datagrid to an xml file using Singleton Design.
i have created
public class DataProvider
{
private static DataProvider singletonInstance = new DataProvider();
private ObservablePerson Person;
/// <summary>
/// Private constructor
/// </summary>
private DataProvider()
{
}
public static DataProvider GetInstance()
{
if (singletonInstance == null)
singletonInstance = new DataProvider();
return singletonInstance;
}
public bool SaveToXml(List<Person> PersonsList)
{
return false;
}
public List<Person> LoadFromX()
{
return new List<Person>() { new Person() { name= "jhon" } };
}
}
}
and this is the object i want to save
[Serializable]
public class ObservablePerson : ObservableObject
{
private string _name;
public string name
{
get
{
return _name;
}
set
{
_name= value;
NotifyPropertyChanged();}
and i also created a view model form from person.
what should i do to save those data in my datagrid in a xml file .
thanks.
Read an XML file using XmlTextReader and call Read method to read its node one by one until the end of file.
using System;
using System.Xml;
namespace ReadXml1 {
class Class1 {
static void Main(string[] args) {
// Create an isntance of XmlTextReader and call Read method to read the file
XmlTextReader textReader = new XmlTextReader("C:\\books.xml");
textReader.Read();
// If the node has value
while (textReader.Read()) {
// Move to fist element
textReader.MoveToElement();
Console.WriteLine("XmlTextReader Properties Test");
Console.WriteLine("===================");
// Read this element's properties and display them on console
Console.WriteLine("Name:" + textReader.Name);
Console.WriteLine("Base URI:" + textReader.BaseURI);
Console.WriteLine("Local Name:" + textReader.LocalName);
Console.WriteLine("Attribute Count:" + textReader.AttributeCount.ToString());
Console.WriteLine("Depth:" + textReader.Depth.ToString());
Console.WriteLine("Line Number:" + textReader.LineNumber.ToString());
Console.WriteLine("Node Type:" + textReader.NodeType.ToString());
Console.WriteLine("Attribute Count:" + textReader.Value.ToString());
}
}
}
}
and for creating and save to XML file:
using System;
using System.Xml;
namespace ReadingXML2 {
class Class1 {
static void Main(string[] args) {
// Create a new file in C:\\ dir
XmlTextWriter textWriter = new XmlTextWriter("C:\\myXmFile.xml", null);
// Opens the document
textWriter.WriteStartDocument();
// Write comments
textWriter.WriteComment("First Comment XmlTextWriter Sample Example");
textWriter.WriteComment("myXmlFile.xml in root dir");
// Write first element
textWriter.WriteStartElement("Student");
textWriter.WriteStartElement("r", "RECORD", "urn:record");
// Write next element
textWriter.WriteStartElement("Name", "");
textWriter.WriteString("Student");
textWriter.WriteEndElement();
// Write one more element
textWriter.WriteStartElement("Address", "");
textWriter.WriteString("Colony");
textWriter.WriteEndElement();
// WriteChars
char[] ch = new char[3];
ch[0] = 'a';
ch[1] = 'r';
ch[2] = 'c';
textWriter.WriteStartElement("Char");
textWriter.WriteChars(ch, 0, ch.Length);
textWriter.WriteEndElement();
// Ends the document.
textWriter.WriteEndDocument();
// close writer
textWriter.Close();
}
}
}
Let's use Xml and Xml.Serialization:
using System.Xml;
using System.Xml.Serialization;
On your singleton, implements
public static List<T> LoadFromXml<T>(string path, string fileName)
{
var xmlSerializer = new XmlSerializer(typeof(List<T>));
var xmlText = File.ReadAllText(Path.Combine(path, fileName));
return (List<T>)xmlSerializer.Deserialize(new StringReader(xmlText));
}
public static bool SaveToXml<T>(List<T> list, string path, string fileName)
{
var xmlText = Serialize<T>(list);
try
{
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
File.WriteAllText(Path.Combine(path, fileName), xmlText);
return true;
}
catch(Exception e)
{
return false;
}
}
private static string Serialize<T>(List<T> list)
{
if (list == null || !list.Any())
return string.Empty;
var xmlSerializer = new XmlSerializer(typeof(List<T>));
using (var stringWriter = new StringWriter())
{
using (var xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings { Indent = true }))
{
xmlSerializer.Serialize(xmlWriter, list);
return stringWriter.ToString();
}
}
}
Using generic T, we can use the same methods to save other types of records
To call:
var person1 = new Person()
{
name = "jhon",
};
var person2 = new Person()
{
name = "another jhon",
};
var personList = new List<Person>();
personList.Add(person1);
personList.Add(person2);
var pathXml = #"C:\testes\xml";
var fileName = "persons.xml";
var dataProvider = DataProvider.GetInstance();
var itsSaved = dataProvider.SaveToXml<Person>(personList, pathXml, fileName);
var persons = dataProvider.LoadFromXml<Person>(pathXml, fileName);
I have a peculiar situation. In a legacy system no longer used we have base64 values stored that we now need to access.
By converting the base64 value to a string I can see that the base64 value contains my properties needed like this.
The problem is that I can't deserialize neither the byte array or the string to a anonymous type object or dynamic. This is because I don't have access to the binaries that this object is using. In this example it is shown as ConsoleApp2.
First try:
public static object FromByteArray(byte[] data)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream(data))
{
object obj = bf.Deserialize(ms);
return obj;
}
}
Source:
https://stackoverflow.com/a/33022788/3850405
System.Runtime.Serialization.SerializationException: 'Unable to find
assembly 'ConsoleApp2, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null'.'
Given that you normally can access properties from a plain Object Class I tried to set every assembly to System.Object with a SerializationBinder.
object o = new { A = "1", B = 2 };
public static object FromByteArray(byte[] data)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream(data))
{
bf.Binder = new PreMergeToMergedDeserializationBinder();
object obj = bf.Deserialize(ms);
return obj;
}
}
}
sealed class PreMergeToMergedDeserializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
var systemObjectAssembly = "System.Object, System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
return Type.GetType(systemObjectAssembly);
}
}
Source:
https://stackoverflow.com/a/9012089/3850405
This prevents any runtime errors but everything that shows up looks like an empty object:
If I try to list properties using the code below it is of course empty as well.
Type myType = myObject.GetType();
List<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
What can I do to deserialize this base64 string and access the properties? Preferably I would not like to create a complete class hierarchy since the original object is quite large.
Runnable example program:
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
object o = new { A = "1", B = 2 };
var base64String = "AAEAAAD/////AQAAAAAAAAAMAgAAAEJDb25zb2xlQXBwMiwgVmVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPW51bGwFAQAAABhDb25zb2xlQXBwMi5FeGFtcGxlTW9kZWwCAAAAHDxNeVByb3BlcnR5QT5rX19CYWNraW5nRmllbGQcPE15UHJvcGVydHlCPmtfX0JhY2tpbmdGaWVsZAEACAIAAAAGAwAAAAtNeVRlc3RWYWx1ZXsAAAAL";
byte[] byteArray = Convert.FromBase64String(base64String);
string objectInfo = System.Text.Encoding.UTF8.GetString(byteArray);
var myObject = FromByteArray(byteArray);
Type myType = myObject.GetType();
List<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
}
public static object FromByteArray(byte[] data)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream(data))
{
bf.Binder = new PreMergeToMergedDeserializationBinder();
object obj = bf.Deserialize(ms);
return obj;
}
}
}
sealed class PreMergeToMergedDeserializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
var systemObjectAssembly = "System.Object, System.Runtime, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
return Type.GetType(systemObjectAssembly);
}
}
}
Guess how the original object could have been stored in the first place:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApp2
{
[Serializable]
public class ExampleModel
{
public string MyPropertyA { get; set; }
public int MyPropertyB { get; set; }
}
class Program
{
static void Main(string[] args)
{
var t = new ExampleModel();
t.MyPropertyA = "MyTestValue";
t.MyPropertyB = 123;
var byteArray = ToByteArray<ExampleModel>(t);
var base64String = Convert.ToBase64String(byteArray);
}
public static byte[] ToByteArray<T>(T obj)
{
if (obj == null)
return null;
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, obj);
return ms.ToArray();
}
}
}
}
This is far from ideal but if you only need small amounts of data it is possible.
I started by looking at the string generated:
\0\u0001\0\0\0����\u0001\0\0\0\0\0\0\0\f\u0002\0\0\0BConsoleApp2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\u0005\u0001\0\0\0\u0018ConsoleApp2.ExampleModel\u0002\0\0\0\u001c<MyPropertyA>k__BackingField\u001c<MyPropertyB>k__BackingField\u0001\0\b\u0002\0\0\0\u0006\u0003\0\0\0\vMyTestValue{\0\0\0\v
From there I could see the values ConsoleApp2.ExampleModel with MyPropertyA and MyPropertyB. We could look at hex for MemberTypeInfo but in my case I used object for every property to save time.
If you want to know more about how the binary format of serialized .NET objects look like and how can it can be interpreted correctly I recommend this thread:
https://stackoverflow.com/a/30176566/3850405
I then created a namespace and a class with the properties for ConsoleApp2.ExampleModel.
namespace ConsoleApp2
{
[Serializable]
public class ExampleModel
{
public object MyPropertyA { get; set; }
public object MyPropertyB { get; set; }
}
}
After that I used the dynamic SerializationBinder from the source below:
sealed class PreMergeToMergedDeserializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToDeserialize = null;
// For each assemblyName/typeName that you want to deserialize to
// a different type, set typeToDeserialize to the desired type.
String exeAssembly = Assembly.GetExecutingAssembly().FullName;
// The following line of code returns the type.
typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
typeName, exeAssembly));
return typeToDeserialize;
}
}
https://stackoverflow.com/a/9012089/3850405
After doing that I could get the values I needed.
In the real example I only matched the actual properties that I needed and ignored the rest.
Complete example:
using ConsoleApp2;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApp2
{
[Serializable]
public class ExampleModel
{
public object MyPropertyA { get; set; }
public object MyPropertyB { get; set; }
}
}
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
object o = new { A = "1", B = 2 };
var base64String = "AAEAAAD/////AQAAAAAAAAAMAgAAAEJDb25zb2xlQXBwMiwgVmVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPW51bGwFAQAAABhDb25zb2xlQXBwMi5FeGFtcGxlTW9kZWwCAAAAHDxNeVByb3BlcnR5QT5rX19CYWNraW5nRmllbGQcPE15UHJvcGVydHlCPmtfX0JhY2tpbmdGaWVsZAEACAIAAAAGAwAAAAtNeVRlc3RWYWx1ZXsAAAAL";
byte[] byteArray = Convert.FromBase64String(base64String);
string objectInfo = System.Text.Encoding.UTF8.GetString(byteArray);
var myObject = FromByteArray<ExampleModel>(byteArray);
Type myType = myObject.GetType();
List<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
}
public static T FromByteArray<T>(byte[] data)
{
if (data == null)
return default(T);
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream(data))
{
bf.Binder = new PreMergeToMergedDeserializationBinder();
object obj = bf.Deserialize(ms);
return (T)obj;
}
}
}
sealed class PreMergeToMergedDeserializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToDeserialize = null;
// For each assemblyName/typeName that you want to deserialize to
// a different type, set typeToDeserialize to the desired type.
String exeAssembly = Assembly.GetExecutingAssembly().FullName;
// The following line of code returns the type.
typeToDeserialize = Type.GetType(String.Format("{0}, {1}",
typeName, exeAssembly));
return typeToDeserialize;
}
}
}
A few lessons learnt:
Values like ConsoleApp2.ExampleModel_someItems without a k__BackingField is probably a field without get and a set methods declared like this:
public object _someItems;
A value like System.Collections.Generic.List`1[[ConsoleApp2.ExampleModelListItem, ConsoleApp2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] needs to be handled individually in SerializationBinder. I did it like this:
if (typeName.Contains("System.Collections.Generic.List") && typeName.Contains("ExampleModelListItem"))
{
var t = new List<ExampleModelListItem>();
typeToDeserialize = t.GetType();
}
If you try to do something like this:
if (typeName.Contains("System.Collections.Generic.List"))
{
var t = new List<object>();
typeToDeserialize = t.GetType();
}
It will result in a exception similar to:
'Object of type 'System.Collections.Generic.List`1[System.Object]' cannot be converted to type 'System.Collections.Generic.List`1[ConsoleApp2.ExampleModelListItem]'.'
Something similar to ConsoleApp2.ExampleModel+EnumTypes means a nested class or enum.
Solved like this:
namespace ConsoleApp2
{
[Serializable]
public class ExampleModel
{
public object MyPropertyA { get; set; }
public enum EnumTypes
{
a
}
}
}
Source:
https://stackoverflow.com/a/2443261/3850405
How do I remove the namespace from the xml response below using Web API?
<ApiDivisionsResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/GrassrootsHoops.Models.Api.Response">
<Divisions xmlns:d2p1="http://schemas.datacontract.org/2004/07/GrassrootsHoops.Data.Entities">
<d2p1:Page>1</d2p1:Page>
<d2p1:PageSize>10</d2p1:PageSize>
<d2p1:Results xmlns:d3p1="http://schemas.datacontract.org/2004/07/GrassrootsHoops.Models.Api.Response.Divisions"/>
<d2p1:Total>0</d2p1:Total>
</Divisions>
</ApiDivisionsResponse>
Option 1 is to switch to using XmlSerializer in GlobalConfiguration:
config.Formatters.XmlFormatter.UseXmlSerializer = true;
Option 2 is to decorate your models with
[DataContract(Namespace="")]
(and if you do so, you'd need to decorate the members with [DataMember] attributes).
If you're willing to decorate your model with XmlRoot, here's a nice way to do it. Suppose you have a car with doors. The default WebApi configuration will return something like :
<car
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<doors>
<door>
<color>black</color>
</door>
</doors>
</car>
This is what you want:
<car>
<doors>
<door>
<color>black</color>
</door>
</doors>
</car>
Here's the model:
[XmlRoot("car")]
public class Car
{
[XmlArray("doors"), XmlArrayItem("door")]
public Door[] Doors { get; set; }
}
What you have to do is create a custom XmlFormatter that will have an empty namespace if there are no namespaces defined in the XmlRoot attribute. For some reason, the default formatter always adds the two default namespaces.
public class CustomNamespaceXmlFormatter : XmlMediaTypeFormatter
{
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
TransportContext transportContext)
{
try
{
var xns = new XmlSerializerNamespaces();
foreach (var attribute in type.GetCustomAttributes(true))
{
var xmlRootAttribute = attribute as XmlRootAttribute;
if (xmlRootAttribute != null)
{
xns.Add(string.Empty, xmlRootAttribute.Namespace);
}
}
if (xns.Count == 0)
{
xns.Add(string.Empty, string.Empty);
}
var task = Task.Factory.StartNew(() =>
{
var serializer = new XmlSerializer(type);
serializer.Serialize(writeStream, value, xns);
});
return task;
}
catch (Exception)
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
}
}
Last thing to do is add the new formatter in the WebApiContext. Be sure to remove (or clear) the old XmlMediaTypeFormatter
public static class WebApiContext
{
public static void Register(HttpConfiguration config)
{
...
config.Formatters.Clear();
config.Formatters.Add(new CustomNamespaceXmlFormatter{UseXmlSerializer=true});
...
}
}
I like pobed2's answer. But I needed the CustomNamespaceXmlFormatter to allow me to specify a default root namespace to be used when the XmlRoot attribute is missing and also when it is present and has no value in the Namespace property (that is, the attribute is used to set the root element name only). So I created an improved version, here it is in case it's useful for someone:
public class CustomNamespaceXmlFormatter : XmlMediaTypeFormatter
{
private readonly string defaultRootNamespace;
public CustomNamespaceXmlFormatter() : this(string.Empty)
{
}
public CustomNamespaceXmlFormatter(string defaultRootNamespace)
{
this.defaultRootNamespace = defaultRootNamespace;
}
public override Task WriteToStreamAsync(
Type type,
object value,
Stream writeStream,
HttpContent content,
TransportContext transportContext)
{
var xmlRootAttribute = type.GetCustomAttribute<XmlRootAttribute>(true);
if(xmlRootAttribute == null)
xmlRootAttribute = new XmlRootAttribute(type.Name)
{
Namespace = defaultRootNamespace
};
else if(xmlRootAttribute.Namespace == null)
xmlRootAttribute = new XmlRootAttribute(xmlRootAttribute.ElementName)
{
Namespace = defaultRootNamespace
};
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, xmlRootAttribute.Namespace);
return Task.Factory.StartNew(() =>
{
var serializer = new XmlSerializer(type, xmlRootAttribute);
serializer.Serialize(writeStream, value, xns);
});
}
}
In the project that keeps response models go to Properties/AssemblyInfo.cs
Add
using System.Runtime.Serialization;
and at the bottom add
[assembly: ContractNamespace("", ClrNamespace = "Project.YourResponseModels")]
Replace Project.YourResponseModels with the actual namespace where response models are located.
You need to add one per namespace
You could use the next algorithm
Put attribute for your class
[XmlRoot("xml", Namespace = "")]
public class MyClass
{
[XmlElement(ElementName = "first_node", Namespace = "")]
public string FirstProperty { get; set; }
[XmlElement(ElementName = "second_node", Namespace = "")]
public string SecondProperty { get; set; }
}
Write method into your Controller or util's class
private ContentResult SerializeWithoutNamespaces(MyClass instanseMyClass)
{
var sw = new StringWriter();
var xmlWriter = XmlWriter.Create(sw, new XmlWriterSettings() {OmitXmlDeclaration = true});
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
var serializer = new XmlSerializer(instanseMyClass.GetType());
serializer.Serialize(xmlWriter, instanseMyClass, ns);
return Content(sw.ToString());
}
Use method SerializeWithoutNamespaces into Action
[Produces("application/xml")]
[Route("api/My")]
public class MyController : Controller
{
[HttpPost]
public ContentResult MyAction(string phrase)
{
var instanseMyClass = new MyClass{FirstProperty ="123", SecondProperty ="789"};
return SerializeWithoutNamespaces(instanseMyClass);
}
}
Don't forget to put some dependencies into StartUp class
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
}
The CustomNamespaceXmlFormatter class did the trick for me except it caused a memory leak (when my web service got hit hard, the memory kept increasing higher and higher), so I modified how the instances of XmlSerializer are created:
public class CustomNamespaceXmlFormatter : XmlMediaTypeFormatter
{
private readonly string defaultRootNamespace;
public CustomNamespaceXmlFormatter() : this(string.Empty)
{
}
public CustomNamespaceXmlFormatter(string defaultRootNamespace)
{
this.defaultRootNamespace = defaultRootNamespace;
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
if (type == typeof(String))
{
//If all we want to do is return a string, just send to output as <string>value</string>
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
else
{
XmlRootAttribute xmlRootAttribute = (XmlRootAttribute)type.GetCustomAttributes(typeof(XmlRootAttribute), true)[0];
if (xmlRootAttribute == null)
xmlRootAttribute = new XmlRootAttribute(type.Name)
{
Namespace = defaultRootNamespace
};
else if (xmlRootAttribute.Namespace == null)
xmlRootAttribute = new XmlRootAttribute(xmlRootAttribute.ElementName)
{
Namespace = defaultRootNamespace
};
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, xmlRootAttribute.Namespace);
return Task.Factory.StartNew(() =>
{
//var serializer = new XmlSerializer(type, xmlRootAttribute); **OLD CODE**
var serializer = XmlSerializerInstance.GetSerializer(type, xmlRootAttribute);
serializer.Serialize(writeStream, value, xns);
});
}
}
}
public static class XmlSerializerInstance
{
public static object _lock = new object();
public static Dictionary<string, XmlSerializer> _serializers = new Dictionary<string, XmlSerializer>();
public static XmlSerializer GetSerializer(Type type, XmlRootAttribute xra)
{
lock (_lock)
{
var key = $"{type}|{xra}";
if (!_serializers.TryGetValue(key, out XmlSerializer serializer))
{
if (type != null && xra != null)
{
serializer = new XmlSerializer(type, xra);
}
_serializers.Add(key, serializer);
}
return serializer;
}
}
}
This works perfectly
public ActionResult JsonAction(string xxx)
{
XmlDocument xmlDoc2 = new XmlDocument();
xmlDoc2.Load(xmlStreamReader);
XDocument d = XDocument.Parse(optdoc2.InnerXml);
d.Root.Attributes().Where(x => x.IsNamespaceDeclaration).Remove();
foreach (var elem in d.Descendants())
elem.Name = elem.Name.LocalName;
var xmlDocument = new XmlDocument();
xmlDocument.Load(d.CreateReader());
var jsonText = JsonConvert.SerializeXmlNode(xmlDocument);
return Content(jsonText);
}
I have written an Interface for writing a very very simple Plugin. In fact it is just a class that is loaded at runtime out of a dll file and is stored as Property in another class. That class that stores the interface has to get serialized. As example this is my serialized object:
<?xml version="1.0" encoding="utf-8"?><MD5HashMapper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.namespace.net" />
But now If i want to load that Object I get an Exception:
As example :
{"<MD5HashMapper xmlns='http://www.vrz.net/Vrz.Map'> was not expected."}
So does anyone has an idea how to solve that problem?
Code:
I have an Interface named IMap that is shared in a dll file to create Addins based on that interface:
public interface IMap
{
object Map(object input);
}
I also have different Mappers (you can pass an input through them and they modify the output). All Mappers are derived from:
[XmlInclude(typeof(ConstMapper))]
[XmlInclude(typeof(FuncMapper))]
[XmlInclude(typeof(IdentMapper))]
[XmlInclude(typeof(NullMapper))]
[XmlInclude(typeof(RefMapper))]
[XmlInclude(typeof(VarMapper))]
[XmlInclude(typeof(TableMapper))]
[XmlInclude(typeof(AddinMapper))]
public class MapperBase:ComponentBase,IMap
{ public virtual object Map(object input) {
throw new NotImplementedException("Diese Methode muss überschrieben werden");
}
public override string ToString() {
return ShortDisplayName;
}
}
Just forget ComponentBase. It is not important for this...
Now i also have a AddinMapper. The main function of that mapper is to cast create MapperBase Object out of the IMap object:
And that is exactly that class I want to seralize including the properties of the Mapper Property (type IMap).
public class AddinMapper : MapperBase
{
private static MapperBase[] _mappers;
const string addinDirectory = #"Addin\Mappers\";
//Mappers from *.dll files are loaded here:
[XmlIgnore]
public static MapperBase[] Mappers
{
get
{
if (_mappers == null)
{
List<MapperBase> maps = new List<MapperBase>();
foreach (string dll in Directory.GetFiles(addinDirectory, "*.dll"))
{
if (Path.GetFileName(dll) != "IMap.dll")
{
var absolutePath = Path.Combine(Environment.CurrentDirectory, dll);
Assembly asm = Assembly.LoadFile(absolutePath);
foreach (Type type in asm.GetTypes().ToList().Where(p => p.GetInterface("IMap") != null))
{
maps.Add(new AddinMapper((IMap)Activator.CreateInstance(type)));
}
}
}
Mappers = maps.ToArray();
}
return _mappers;
}
set
{
_mappers = value;
}
}
IMap _base;
public string MapperString { get; set; }
[XmlIgnore()]
public IMap Mapper
{
get
{
if (_base == null)
{
Type type = null;
foreach (MapperBase mapperBase in Mappers)
{
if (mapperBase is AddinMapper && ((AddinMapper)mapperBase).Mapper.GetType().FullName == _mapperName)
{
type = (mapperBase as AddinMapper).Mapper.GetType();
break;
}
}
if (type != null)
{
XmlSerializer serializer = new XmlSerializer(type);
using (StringReader reader = new StringReader(MapperString))
{
Mapper = (IMap)serializer.Deserialize(reader);
}
}
}
return _base;
}
private set
{
_base = value;
StoreMapperString();
}
}
string _mapperName;
[System.ComponentModel.Browsable(false)]
public string MapperName
{
get
{
return _mapperName;
}
set
{
_mapperName = value;
}
}
public AddinMapper(IMap baseInterface) : this()
{
Mapper = baseInterface;
_mapperName = baseInterface.GetType().FullName;
}
public AddinMapper()
{
}
public override object Map(object input)
{
return Mapper.Map(input);
}
public override string ToString()
{
return Mapper.ToString();
}
private void StoreMapperString()
{
MemoryStream memstream = new MemoryStream();
XmlStore.SaveObject(memstream, Mapper);
using (StreamReader reader = new StreamReader(memstream))
{
memstream.Position = 0;
MapperString = reader.ReadToEnd();
}
}
}
An example for such a addin would be:
public class ReplaceMapper : IMap
{
public string StringToReplace { get; set; }
public string StringToInsert { get; set; }
public object Map(object input)
{
if (input is string)
{
input = (input as string).Replace(StringToReplace, StringToInsert);
}
return input;
}
}
And the Problem is I want to save the Settings like StringToReplace,... as xml
I ve solved my problem:
I really don t know why but take a look at this article: http://www.calvinirwin.net/2011/02/10/xmlserialization-deserialize-causes-xmlns-was-not-expected/
(if link is dead later)
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = elementName;
xRoot.IsNullable = true;
XmlSerializer ser = new XmlSerializer(typeof(MyObject), xRoot);
XmlReader xRdr = XmlReader.Create(new StringReader(xmlData));
MyObject tvd = (MyObject)ser.Deserialize(xRdr);
Now the important thing: It does not matter if you don t get an excption on serialization. You have to add the XmlRootAttribute on both ways: Serialisation and Deserialization.
UPDATE: You can run the code at the end of this to recreate and see the error I am having and hopefully solve it!
UPDATE2: It's not the removal of the xmlns="" that's the issue... as you can remove it from the initial xml string. The problem is with the [XmlType(TypeName = "Systems")] somehow causing it to be added...
UPDATE3: Turns out the problem is in here, I need to set the TypeName based on what is in the existing, XmlTypeAttribute if it already exists on the class....
xmlAttributes.XmlType = new XmlTypeAttribute
{
Namespace = ""
};
I get the following XML as a string from a webservice
<Systems xmlns="">
<System id="1">
<sys_name>ALL</sys_name>
</System>
<System id="2">
<sys_name>asdfasdf</sys_name>
</System>
<System id="3">
<sys_name>fasdfasf</sys_name>
</System>
<System id="4">
<sys_name>asdfasdfasdf</sys_name>
</System>
</Systems>
I then execute this, to convert it to an object
result = XElement.Parse(xmlResult.OuterXml).Deserialize<AwayRequestSystems>();
Strangely though, in the Deserialize method, while the RemoveAllNamespaces works and returns the xml without the namespace
I get the error <Systems xmlns=''> was not expected. in the catch when return (T) serializer.Deserialize(reader); executes!
Why is it doing this? The xmlns is GONE!!!
EXECUTABLE CODE! (Just put it in a test project)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Xml.Serialization;
namespace DeserializationTest
{
[TestClass]
public class UnitTest1
{
public TestContext TestContext { get; set; }
[TestMethod]
public void RemoveXmlnsFromSystems()
{
var xml = XElement.Parse(#"<Systems xmlns="""">
<System id=""1"">
<sys_name>ALL</sys_name>
</System>
<System id=""2"">
<sys_name>ePO</sys_name>
</System>
<System id=""3"">
<sys_name>iEFT</sys_name>
</System>
<System id=""4"">
<sys_name>Away Requests</sys_name>
</System>
<System id=""5"">
<sys_name>RP3</sys_name>
</System>
</Systems>");
var systems = xml.Deserialize<AwayRequestSystems>();
Assert.IsInstanceOfType(systems, typeof(AwayRequestSystems));
var xmlnsFree = xml.RemoveAllNamespaces();
var str = xmlnsFree.ToString();
Debug.WriteLine(str);
Assert.AreNotEqual("Error", xmlnsFree.Name.ToString(), "Serialization Error");
Assert.IsFalse(str.Contains("xmlns"), "Xmlns still exists");
}
}
[XmlType(TypeName = "Systems")]
public class AwayRequestSystems : List<AwayRequestSystem> { }
[XmlType(TypeName = "System")]
public class AwayRequestSystem
{
[XmlAttribute("id")]
public int ID { get; set; }
[XmlElement("sys_name")]
public string Name { get; set; }
}
public static class XmlSerializerFactory
{
private static Dictionary<Type, XmlSerializer> _serializers = new Dictionary<Type, XmlSerializer>();
public static void ResetCache()
{
_serializers = new Dictionary<Type, XmlSerializer>();
}
public static XmlSerializer GetSerializerFor(Type typeOfT)
{
if (!_serializers.ContainsKey(typeOfT))
{
var xmlAttributes = new XmlAttributes();
var xmlAttributeOverrides = new XmlAttributeOverrides();
Debug.WriteLine(string.Format("XmlSerializerFactory.GetSerializerFor(typeof({0}));", typeOfT));
xmlAttributes.XmlType = new XmlTypeAttribute
{
Namespace = ""
};
xmlAttributes.Xmlns = false;
var types = new List<Type> { typeOfT, typeOfT.BaseType };
foreach (var property in typeOfT.GetProperties())
{
types.Add(property.PropertyType);
}
types.RemoveAll(t => t.ToString().StartsWith("System."));
foreach (var type in types)
{
if (xmlAttributeOverrides[type] == null)
xmlAttributeOverrides.Add(type, xmlAttributes);
}
var newSerializer = new XmlSerializer(typeOfT, xmlAttributeOverrides);
//var newSerializer = new XmlSerializer(typeOfT, xmlAttributeOverrides, types.ToArray(), new XmlRootAttribute(), string.Empty);
//var newSerializer = new XmlSerializer(typeOfT, string.Empty);
_serializers.Add(typeOfT, newSerializer);
}
return _serializers[typeOfT];
}
}
public static class XElementExtensions
{
public static XElement RemoveAllNamespaces(this XElement source)
{
if (source.HasAttributes)
source.Attributes().Where(a => a.Name.LocalName.Equals("xmlns")).Remove();
return source.HasElements
? new XElement(source.Name.LocalName,
source.Attributes()/*.Where(a => !a.Name.LocalName.Equals("xmlns"))*/,
source.Elements().Select(el => RemoveAllNamespaces(el))
)
: new XElement(source.Name.LocalName)
{
Value = source.Value
};
}
}
public static class SerializationExtensions
{
public static XElement Serialize(this object source)
{
try
{
var serializer = XmlSerializerFactory.GetSerializerFor(source.GetType());
var xdoc = new XDocument();
using (var writer = xdoc.CreateWriter())
{
serializer.Serialize(writer, source, new XmlSerializerNamespaces(new[] { new XmlQualifiedName("", "") }));
}
var result = (xdoc.Document != null) ? xdoc.Document.Root : new XElement("Error", "Document Missing");
return result.RemoveAllNamespaces();
}
catch (Exception x)
{
return new XElement("Error", x.ToString());
}
}
public static T Deserialize<T>(this XElement source) where T : class
{
//try
//{
var serializer = XmlSerializerFactory.GetSerializerFor(typeof(T));
var cleanxml = source.RemoveAllNamespaces();
var reader = cleanxml.CreateReader();
return (T)serializer.Deserialize(reader);
//}
//catch (Exception x)
//{
// return null;
//}
}
}
}
The problem as it turns out was because I was losing the TypeName attribute when removing namespaces with this line
xmlAttributes.XmlType = new XmlTypeAttribute
{
Namespace = ""
};
I changed the GetSerializerFor class in the factory to the following and it now works
public static XmlSerializer GetSerializerFor(Type typeOfT)
{
if (!_serializers.ContainsKey(typeOfT))
{
Debug.WriteLine(string.Format("XmlSerializerFactory.GetSerializerFor(typeof({0}));", typeOfT));
var types = new List<Type> { typeOfT, typeOfT.BaseType };
foreach (var property in typeOfT.GetProperties())
{
types.Add(property.PropertyType);
}
types.RemoveAll(t => t.ToString().StartsWith("System."));
var xmlAttributeOverrides = new XmlAttributeOverrides();
foreach (var type in types)
{
if (xmlAttributeOverrides[type] != null)
continue;
var xmlAttributes = new XmlAttributes
{
XmlType = new XmlTypeAttribute
{
Namespace = "",
TypeName = GetSerializationTypeName(type)
},
Xmlns = false
};
xmlAttributeOverrides.Add(type, xmlAttributes);
}
var newSerializer = new XmlSerializer(typeOfT, xmlAttributeOverrides);
//var newSerializer = new XmlSerializer(typeOfT, xmlAttributeOverrides, types.ToArray(), new XmlRootAttribute(), string.Empty);
//var newSerializer = new XmlSerializer(typeOfT, string.Empty);
_serializers.Add(typeOfT, newSerializer);
}
return _serializers[typeOfT];
}
private static string GetSerializationTypeName(Type type)
{
var xmlTypeAttribute = TypeDescriptor.GetAttributes(type)
.OfType<XmlTypeAttribute>().FirstOrDefault();
var typeName = xmlTypeAttribute.TypeName;
return string.IsNullOrEmpty(typeName) ? type.Name : typeName;
}