Why is Equals method called before full deserialization of an object? - c#

I want to serialize and deserialize objects, which are contained in a dictionary, implement Equals method and contain dictionaries inside. However it leads to an ArgumentException, because Equals method is called too early on deserialization.
using System.Collections.Generic;
using System.Runtime.Serialization;
using NUnit.Framework;
[Serializable]
public class SerializableObject
{
public Dictionary<string, bool> Values = new Dictionary<string, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var (k, v) in Values)
if (!other.Values.TryGetValue(k, out var otherV) || v != otherV)
return false;
return true;
}
public override int GetHashCode()
{
return 0;
}
}
[Test]
public void DeserializeDictionary()
{
using (var stream = new MemoryStream())
{
var obj1 = new SerializableObject();
obj1.Values["aaa"] = true;
var obj2 = new SerializableObject();
obj2.Values["bbb"] = false;
var formatter = new BinaryFormatter();
var dict = new Dictionary<SerializableObject, bool>
{
[obj1] = true,
[obj2] = false
};
formatter.Serialize(stream, dict);
stream.Seek(0, SeekOrigin.Begin);
dict = (Dictionary<SerializableObject, bool>)formatter.Deserialize(stream);
Assert.Contains(obj1, dict.Keys);
Assert.Contains(obj2, dict.Keys);
}
}
When deserializing, Equals method is called on two objects, which have empty dictionaries, and an ArgumentException happens ("An item with the same key has already been added").
Also, if the object implements [OnDeserialized] method, this method is also called when the dictionary is still empty, and the inner dictionary is filled later, before returning the entire top-level dictionary.
EDIT: In this short example GetHashCode deliberately returns 0 to force Equals to be called. In the real-world program there are just two unequal objects with the same hash code (a collision).
EDIT 2: Here is a more complex example where the objects are nested.
[Serializable]
public class SerializableObject : ISerializable
{
public SerializableObject() {}
public Dictionary<string, bool> Values = new Dictionary<string, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var kvp in Values)
if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
return false;
return true;
}
public override int GetHashCode() => 0;
public void GetObjectData(SerializationInfo info, StreamingContext context) =>
info.AddValue(nameof(Values), Values.ToArray());
private SerializableObject(SerializationInfo info, StreamingContext context) =>
Values = new Dictionary<string, bool>(info.Get<KeyValuePair<string, bool>[]>(nameof(Values)));
}
[Serializable]
public class SerializableObject2 : ISerializable
{
public SerializableObject2() { }
public Dictionary<SerializableObject, bool> Values = new Dictionary<SerializableObject, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject2 other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var kvp in Values)
if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
return false;
return true;
}
public override int GetHashCode() => 0;
public void GetObjectData(SerializationInfo info, StreamingContext context) =>
info.AddValue(nameof(Values), Values.ToArray());
private SerializableObject2(SerializationInfo info, StreamingContext context) =>
Values = new Dictionary<SerializableObject, bool>(info.Get<KeyValuePair<SerializableObject, bool>[]>(nameof(Values)));
}
[Test]
public void DeserializeDictionary()
{
using (var stream = new MemoryStream())
{
var obj1 = new SerializableObject();
obj1.Values["aaa"] = true;
var obj2 = new SerializableObject();
obj2.Values["bbb"] = false;
var sobj1 = new SerializableObject2
{
Values = new Dictionary<SerializableObject, bool>
{
[obj1] = true,
[obj2] = false
}
};
var formatter = new BinaryFormatter();
formatter.Serialize(stream, sobj1);
stream.Seek(0, SeekOrigin.Begin);
sobj1 = (SerializableObject2)formatter.Deserialize(stream);
Assert.Contains(obj1, sobj1.Values.Keys);
Assert.Contains(obj2, sobj1.Values.Keys);
}
}

I'm not sure and am just speculating, but it doesn't look like the SerializableObject is being populated until after the Dictionary that was serialized (in this case, dict) has added them.
It does seem like you can get around the issue by having SerializableObject implement ISerializable and provide your own GetObjectData and Deserialization constructor.
[Serializable]
public class SerializableObject : ISerializable
{
public SerializableObject()
{
}
public Dictionary<string, bool> Values = new Dictionary<string, bool>();
public override bool Equals(object obj)
{
if (!(obj is SerializableObject other))
return false;
if (ReferenceEquals(this, obj))
return true;
if (Values.Count != other.Values.Count)
return false;
foreach (var kvp in Values)
if (!other.Values.TryGetValue(kvp.Key, out var otherV) || kvp.Value != otherV)
return false;
return true;
}
public override int GetHashCode()
{
return 0;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(KeyValuePairsKey, Values.Select(kvp => kvp).ToArray());
}
private SerializableObject(SerializationInfo info, StreamingContext context)
{
var kvps = info.GetValue(KeyValuePairsKey, typeof(KeyValuePair<string, bool>[])) as KeyValuePair<string, bool>[];
foreach (var kvp in kvps)
{
Values.Add(kvp.Key, kvp.Value);
}
}
private const string KeyValuePairsKey = "KVPS";
}
class Program
{
static void Main(string[] args)
{
using (var stream = new MemoryStream())
{
var obj1 = new SerializableObject();
obj1.Values["aaa"] = true;
var obj2 = new SerializableObject();
obj2.Values["bbb"] = false;
var formatter = new BinaryFormatter();
var dict = new Dictionary<SerializableObject, bool>
{
[obj1] = true,
[obj2] = false
};
formatter.Serialize(stream, dict);
stream.Seek(0, SeekOrigin.Begin);
dict = (Dictionary<SerializableObject, bool>)formatter.Deserialize(stream);
}
}
}

You always return 0 in the method GetHashCode(). I think the dictionary uses the GetHashCode() to determine the equality of keys.

Related

Remove DefaultValue attribute for XML serialization in C#

I am serializing an object into XML which has DefaultValue attributes on some of the properties. Under certain circumstances I would like to disable all these default values during serialization. Is there a way to remove the attribute during runtime?
[Serializable]
[XmlType(TypeName = "MyType")]
public class MyType
{
public MyType()
{
MyValue = false;
}
[XmlElement(ElementName = "myValue", Form = XmlSchemaForm.Unqualified)]
[DefaultValue(false)]
public bool MyValue { get; set; }
}
public class TestSerializer
{
public void Serialize()
{
XmlSerializer serializer = new XmlSerializer(typeof(MyType));
using (TextWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
serializer.Serialize(writer, new MyType());
}
}
}
I was already looking into doing that with reflection, but could not get it working. The XmlAttributeOverrides also do not seem to help, but maybe I just haven't found the correct approach yet. Any ideas?
XmlSerializerIgnoringDefaultValuesKey.Create from this answer to Force XML serialization of XmlDefaultValue values mostly does what you need, however you have overridden the XML element name of MyValue by applying [XmlElement(ElementName = "myValue", Form = XmlSchemaForm.Unqualified)], which is stated to be not implemented in that answer:
When overriding with this XmlAttributes object any attributes that are desired to stay need to be transferred into this new object.
The following enhanced version supports transferring [XmlElement], [XmlArray] and [XmlArrayItem] override attributes:
public abstract class XmlSerializerKey
{
static class XmlSerializerHashTable
{
static Dictionary<object, XmlSerializer> dict;
static XmlSerializerHashTable()
{
dict = new Dictionary<object, XmlSerializer>();
}
public static XmlSerializer GetSerializer(XmlSerializerKey key)
{
lock (dict)
{
XmlSerializer value;
if (!dict.TryGetValue(key, out value))
dict[key] = value = key.CreateSerializer();
return value;
}
}
}
readonly Type serializedType;
protected XmlSerializerKey(Type serializedType)
{
this.serializedType = serializedType;
}
public Type SerializedType { get { return serializedType; } }
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
return true;
else if (ReferenceEquals(null, obj))
return false;
if (GetType() != obj.GetType())
return false;
XmlSerializerKey other = (XmlSerializerKey)obj;
if (other.serializedType != serializedType)
return false;
return true;
}
public override int GetHashCode()
{
int code = 0;
if (serializedType != null)
code ^= serializedType.GetHashCode();
return code;
}
public override string ToString()
{
return string.Format(base.ToString() + ": for type: " + serializedType.ToString());
}
public XmlSerializer GetSerializer()
{
return XmlSerializerHashTable.GetSerializer(this);
}
protected abstract XmlSerializer CreateSerializer();
}
public abstract class XmlSerializerWithExtraTypesKey : XmlSerializerKey
{
static IEqualityComparer<HashSet<Type>> comparer;
readonly HashSet<Type> extraTypes = new HashSet<Type>();
static XmlSerializerWithExtraTypesKey()
{
comparer = HashSet<Type>.CreateSetComparer();
}
protected XmlSerializerWithExtraTypesKey(Type serializedType, IEnumerable<Type> extraTypes)
: base(serializedType)
{
if (extraTypes != null)
foreach (var type in extraTypes)
this.extraTypes.Add(type);
}
public Type[] ExtraTypes { get { return extraTypes.ToArray(); } }
public override bool Equals(object obj)
{
if (!base.Equals(obj))
return false;
XmlSerializerWithExtraTypesKey other = (XmlSerializerWithExtraTypesKey)obj;
return comparer.Equals(this.extraTypes, other.extraTypes);
}
public override int GetHashCode()
{
int code = base.GetHashCode();
if (extraTypes != null)
code ^= comparer.GetHashCode(extraTypes);
return code;
}
}
public sealed class XmlSerializerIgnoringDefaultValuesKey : XmlSerializerWithExtraTypesKey
{
readonly XmlAttributeOverrides overrides;
private XmlSerializerIgnoringDefaultValuesKey(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, XmlAttributeOverrides overrides)
: base(serializerType, ignoreDefaultTypes)
{
this.overrides = overrides;
}
public static XmlSerializerIgnoringDefaultValuesKey Create(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, bool recurse)
{
XmlAttributeOverrides overrides;
Type [] typesWithOverrides;
CreateOverrideAttributes(ignoreDefaultTypes, recurse, out overrides, out typesWithOverrides);
return new XmlSerializerIgnoringDefaultValuesKey(serializerType, typesWithOverrides, overrides);
}
protected override XmlSerializer CreateSerializer()
{
var types = ExtraTypes;
if (types == null || types.Length < 1)
return new XmlSerializer(SerializedType);
return new XmlSerializer(SerializedType, overrides);
}
static void CreateOverrideAttributes(IEnumerable<Type> types, bool recurse, out XmlAttributeOverrides overrides, out Type[] typesWithOverrides)
{
HashSet<Type> visited = new HashSet<Type>();
HashSet<Type> withOverrides = new HashSet<Type>();
overrides = new XmlAttributeOverrides();
foreach (var type in types)
{
CreateOverrideAttributes(type, recurse, overrides, visited, withOverrides);
}
typesWithOverrides = withOverrides.ToArray();
}
static void AddOverride(XmlAttributeOverrides overrides, Type type, MemberInfo memberInfo)
{
var xmlElementAttr = memberInfo.GetCustomAttributes<XmlElementAttribute>();
var xmlArrayAttr = memberInfo.GetCustomAttribute<XmlArrayAttribute>();
var xmlArrayItemAttr = memberInfo.GetCustomAttributes<XmlArrayItemAttribute>();
var attrs = new XmlAttributes
{
XmlDefaultValue = null,
XmlArray = xmlArrayAttr,
};
foreach (var a in xmlElementAttr)
attrs.XmlElements.Add(a);
foreach (var a in xmlArrayItemAttr)
attrs.XmlArrayItems.Add(a);
overrides.Add(type, memberInfo.Name, attrs);
}
static void CreateOverrideAttributes(Type type, bool recurse, XmlAttributeOverrides overrides, HashSet<Type> visited, HashSet<Type> withOverrides)
{
if (type == null || type == typeof(object) || type.IsPrimitive || type == typeof(string) || visited.Contains(type))
return;
foreach (var property in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
if (overrides[type, property.Name] == null) // Check to see if overrides for this base type were already set.
if (Attribute.IsDefined(property, typeof(DefaultValueAttribute), true))
{
withOverrides.Add(type);
AddOverride(overrides, type, property);
}
foreach (var field in type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
if (overrides[type, field.Name] == null) // Check to see if overrides for this base type were already set.
if (Attribute.IsDefined(field, typeof(DefaultValueAttribute), true))
{
withOverrides.Add(type);
AddOverride(overrides, type, field);
}
visited.Add(type);
if (recurse)
{
var baseType = type.BaseType;
if (baseType != type)
CreateOverrideAttributes(baseType, recurse, overrides, visited, withOverrides);
}
}
}
And use it like:
var serializer = XmlSerializerIgnoringDefaultValuesKey.Create(typeof(MyType), new[] { typeof(MyType) }, true).GetSerializer();
using (TextWriter writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
serializer.Serialize(writer, new MyType());
}
Demo fiddle here.

Deserialize JSON.NET not knowing the type [duplicate]

This question already has answers here:
Deserializing polymorphic json classes without type information using json.net
(6 answers)
Closed 5 years ago.
So i'm coding a tcp/ip chat, and i wanted to use json.net to serialize the messages so i can send it through a network.
I got these two types of message:
class AlertMessage
{
public string Client { get; set; }
public ServerManager.Status Status { get; set; } //enum {Connect, Disconnect}
}
class TextMessage
{
public string Client { get; set; }
public string Message { get; set; }
}
to deserialize i need to use the method JsonConvert.DeserializeObject<T>(object), but on the client side i cant realy tell what type arrives, any help is welcome! thanks
If you have no idea about the json you are going to receive, you could use the
using System.Web.Script.Serialization;
namespace. (Easily search and add this reference)
I used the following code to convert any object into a valid json object.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;
namespace MinimalMonitoringClient.Helper
{
public sealed class DynamicJsonConverter : JavaScriptConverter
{
public static object ConvertToObject(string data)
{
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
return serializer.Deserialize(data, typeof(object));
}
private static string CleanJson(string data)
{
//Remove leading and ending whitespaces.
data = data.Trim();
//Java Script notation fixes
if(data.StartsWith("["))
data = data.Remove(0, 1);
if(data.EndsWith("]"))
data = data.Remove(data.Length - 1, 1);
return data.Contains("={") ? data.Replace("={", ":{") : data;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
return type == typeof(object) ? new DynamicJsonObject(dictionary) : null;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get { return new ReadOnlyCollection<Type>(new List<Type>(new[] { typeof(object) })); }
}
#region Nested type: DynamicJsonObject
private sealed class DynamicJsonObject : DynamicObject
{
private readonly IDictionary<string, object> _dictionary;
public DynamicJsonObject(IDictionary<string, object> dictionary)
{
_dictionary = dictionary ?? throw new ArgumentNullException("dictionary");
}
public override string ToString()
{
var sb = new StringBuilder("{");
ToString(sb);
return sb.ToString();
}
private void ToString(StringBuilder sb)
{
var firstInDictionary = true;
foreach (var pair in _dictionary)
{
if (!firstInDictionary)
sb.Append(",");
firstInDictionary = false;
var value = pair.Value;
var name = pair.Key;
if (value is string)
{
sb.AppendFormat("{0}:\"{1}\"", name, value);
}
else if (value is IDictionary<string, object>)
{
new DynamicJsonObject((IDictionary<string, object>)value).ToString(sb);
}
else if (value is ArrayList)
{
sb.Append(name + ":[");
var firstInArray = true;
foreach (var arrayValue in (ArrayList)value)
{
if (!firstInArray)
sb.Append(",");
firstInArray = false;
if (arrayValue is IDictionary<string, object>)
new DynamicJsonObject((IDictionary<string, object>)arrayValue).ToString(sb);
else if (arrayValue is string)
sb.AppendFormat("\"{0}\"", arrayValue);
else
sb.AppendFormat("{0}", arrayValue);
}
sb.Append("]");
}
else
{
sb.AppendFormat("{0}:{1}", name, value);
}
}
sb.Append("}");
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (!_dictionary.TryGetValue(binder.Name, out result))
{
// return null to avoid exception. caller can check for null this way...
result = null;
return true;
}
result = WrapResultObject(result);
return true;
}
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
if (indexes.Length == 1 && indexes[0] != null)
{
if (!_dictionary.TryGetValue(indexes[0].ToString(), out result))
{
// return null to avoid exception. caller can check for null this way...
result = null;
return true;
}
result = WrapResultObject(result);
return true;
}
return base.TryGetIndex(binder, indexes, out result);
}
private static object WrapResultObject(object result)
{
var dictionary = result as IDictionary<string, object>;
if (dictionary != null)
return new DynamicJsonObject(dictionary);
var arrayList = result as ArrayList;
if (arrayList != null && arrayList.Count > 0)
{
return arrayList[0] is IDictionary<string, object>
? new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJsonObject(x)))
: new List<object>(arrayList.Cast<object>());
}
return result;
}
}
#endregion
}
}
Now simply convert any json into an object with
object jsonObject = DynamicJsonConverter.ConvertToObject(Json);
Hope that helps
Not sure if I have understood your question correctly, but you can Deserialize object it as dynamic. For example:
var converter = new ExpandoObjectConverter();
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(json, converter);

With C# how to pass list of dynamically generated collection as JSON array to WCF web service

My objective:
To create a list of objects with dynamically generated properties which can be passed to WCF service parameter as a JSON array.
Final outcome is the following:
[{id:2, name:"John", country:"Germany"},...] or
[{id:3, city:"Sydney"},...]
From WCF I don't have the convenience of working with a class, i.e. "Thing" so I could do this:
public List<Thing> MyThings {get; set;}
I do not know what the properties are at run time. I have experimented with System.Dynamic namespace using
List<ExpandoObject>
Unfortunately, dynamic code will give me the error message:
Dynamic operations can only be performed in homogenous AppDomain.
To allow dynamic code to run I would have to change legacyCasModel tag in the web.config. Unfortunately I cannot change the web.config.
Another option is to use Dictionary array but I do not want the key/value pair format:
[{key:id}, {value:2}]
for I'm interested in
[{id:2, name:"John"}]
My challenge is that I don't know what the properties are at run time otherwise it would be easy to convert a List<T> to array.
Any suggestion?
To pass list of dynamically generated collection as JSON array to WCF web service, you can use ExpandoObject and any serializer, I have used Newtonsoft.Json
dynamic obj1 = new ExpandoObject();
obj1.id = 1;
obj1.name = "TPS";
dynamic obj2 = new ExpandoObject();
obj2.id = 2;
obj2.name = "TPS_TPS";
var Objects = new List<ExpandoObject>();
Objects.Add(flexible);
Objects.Add(flexible2);
var serialized = JsonConvert.SerializeObject(l); // JsonConvert - from Newtonsoft.Json
To Deserialize and retrieve the object dynamically i have used JavaScriptSerializer, you can also use System.Web.Helpers.Json.Decode()
All you need is this code and reference to a System.Web.Extensions from your project :
public sealed class DynamicJsonConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
return type == typeof(object) ? new DynamicJsonObject(dictionary) : null;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get { return new ReadOnlyCollection<Type>(new List<Type>(new[] { typeof(object) })); }
}
#region Nested type: DynamicJsonObject
private sealed class DynamicJsonObject : DynamicObject
{
public readonly IDictionary<string, object> _dictionary;
public DynamicJsonObject(IDictionary<string, object> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
public override string ToString()
{
var sb = new StringBuilder("{");
ToString(sb);
return sb.ToString();
}
private void ToString(StringBuilder sb)
{
var firstInDictionary = true;
foreach (var pair in _dictionary)
{
if (!firstInDictionary)
sb.Append(",");
firstInDictionary = false;
var value = pair.Value;
var name = pair.Key;
if (value is string)
{
sb.AppendFormat("{0}:\"{1}\"", name, value);
}
else if (value is IDictionary<string, object>)
{
new DynamicJsonObject((IDictionary<string, object>)value).ToString(sb);
}
else if (value is ArrayList)
{
sb.Append(name + ":[");
var firstInArray = true;
foreach (var arrayValue in (ArrayList)value)
{
if (!firstInArray)
sb.Append(",");
firstInArray = false;
if (arrayValue is IDictionary<string, object>)
new DynamicJsonObject((IDictionary<string, object>)arrayValue).ToString(sb);
else if (arrayValue is string)
sb.AppendFormat("\"{0}\"", arrayValue);
else
sb.AppendFormat("{0}", arrayValue);
}
sb.Append("]");
}
else
{
sb.AppendFormat("{0}:{1}", name, value);
}
}
sb.Append("}");
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (!_dictionary.TryGetValue(binder.Name, out result))
{
// return null to avoid exception. caller can check for null this way...
result = null;
return true;
}
result = WrapResultObject(result);
return true;
}
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
if (indexes.Length == 1 && indexes[0] != null)
{
if (!_dictionary.TryGetValue(indexes[0].ToString(), out result))
{
// return null to avoid exception. caller can check for null this way...
result = null;
return true;
}
result = WrapResultObject(result);
return true;
}
return base.TryGetIndex(binder, indexes, out result);
}
private static object WrapResultObject(object result)
{
var dictionary = result as IDictionary<string, object>;
if (dictionary != null)
return new DynamicJsonObject(dictionary);
var arrayList = result as ArrayList;
if (arrayList != null && arrayList.Count > 0)
{
return arrayList[0] is IDictionary<string, object>
? new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJsonObject(x)))
: new List<object>(arrayList.Cast<object>());
}
return result;
}
}
#endregion
}
How to Use :
string jsonString = ".......";
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
dynamic data = serializer.Deserialize(jsonString, typeof(object));
foreach (var d in data)
{
var id = d.id;
var name = d.name;
}

NHibernate - Mapping a Complex Type so that is Serialized as String

I have a DTO Object with a Property of Type "ObservableDictionary" named "Test" which is also Serializable!
The DTO Field "Test" is now mapped to a Datbasfield "Test" which is of Type nvarchar(max).
When I save and load also everything works fine, but the serialization in the database is not readable, it's filled with special chars.
Is it possible to tell NHibernate to use the XMLSerializer?
Ok, I got it! With Help from Rippo!
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;
namespace MCC.Common.DL.BaseObjects
{
public class SerializableUserType<originalType> : IUserType
{
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.String);
return types;
}
}
public System.Type ReturnedType
{
get { return typeof(originalType); }
}
public new bool Equals(object x, object y)
{
if (x == null)
{
return false;
}
else
{
return x.Equals(y);
}
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
string txt = (string)NHibernateUtil.String.NullSafeGet(rs, names[0]);
return StringSerializer<originalType>.DeSerialize(txt);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
NHibernateUtil.String.NullSafeSet(cmd, null, index);
return;
}
var wrt = StringSerializer<originalType>.Serialize((originalType)value);
NHibernateUtil.String.NullSafeSet(cmd, wrt, index);
}
public object DeepCopy(object value)
{
if (value == null) return null;
return StringSerializer<originalType>.DeSerialize(StringSerializer<originalType>.Serialize((originalType)value));
}
public bool IsMutable
{
get { return false; }
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
}
}
and this I use in Fluent like :
Map(x => x.ScriptedTagScript).CustomType(typeof(SerializableUserType<ObservableDictionary<string, string>>));
And you need this helper class:
public class StringSerializer<T>
{
public static string Serialize(T obj)
{
if (obj == null)
return string.Empty;
// XML-Serialisieren in String
XmlSerializer serializer = new XmlSerializer(obj.GetType());
// Serialisieren in MemoryStream
MemoryStream ms = new MemoryStream();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(ms, settings);
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
serializer.Serialize(writer, obj, namespaces);
// Stream in String umwandeln
StreamReader r = new StreamReader(ms);
r.BaseStream.Seek(0, SeekOrigin.Begin);
return r.ReadToEnd();
}
public static T DeSerialize(string txt)
{
T retVal = default(T);
if (string.IsNullOrEmpty(txt))
{
return retVal;
}
try
{
XmlSerializer ser = new XmlSerializer(typeof (T));
StringReader stringReader = new StringReader(txt);
XmlTextReader xmlReader = new XmlTextReader(stringReader);
retVal = (T) ser.Deserialize(xmlReader);
xmlReader.Close();
stringReader.Close();
}
catch (Exception)
{
}
return retVal;
}
}

Custom mediatypeformatter not working on inherited classes

I have this line:
GlobalConfiguration.Configuration.Formatters.Add(New ExcelMediaTypeFormatter(Of Classification)(Function(t) New ExcelRow(ExcelCell.Map(t.ChemicalAbstractService), ExcelCell.Map(t.Substance), ExcelCell.Map(t.Columns("Classifidcation")), ExcelCell.Map(t.Columns("Classification"))), Function(format) "excel"))
It works fine and creates a excelfile from my web api.
I have several subclasses that inherits this Classification class and I want to make a mediaformatter for each subclass for getting specific columns in the excelformatter.
The problems is that if I do like this:
GlobalConfiguration.Configuration.Formatters.Add(New ExcelMediaTypeFormatter(Of CustomClassification)(Function(t) New ExcelRow(ExcelCell.Map(t.ChemicalAbstractService), ExcelCell.Map(t.Substance), ExcelCell.Map(t.Columns("Classifidcation")), ExcelCell.Map(t.Columns("Classification"))), Function(format) "excel"))
Then it doesn't work at all. It just generates xml from the standard formatter instead. How can I make it react to a subclass, when the web api returns a
IQueryable(Of Classification)
The formatter:
public class ExcelMediaTypeFormatter<T> : BufferedMediaTypeFormatter
{
private const string ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private readonly Func<T, ExcelRow> builder;
public ExcelMediaTypeFormatter(Func<T, ExcelRow> value)
{
builder = value;
SupportedMediaTypes.Add(new MediaTypeHeaderValue(ContentType));
}
public ExcelMediaTypeFormatter(Func<T, ExcelRow> value, params Func<object, string>[] type)
: this(value)
{
foreach (var mediaTypeMapping in type) {
this.MediaTypeMappings.Add(Map(mediaTypeMapping));
}
}
public override bool CanWriteType(Type type)
{
return type == typeof(IQueryable<T>) || type == typeof(T);
}
public override bool CanReadType(Type type)
{
return false;
}
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
using (Stream ms = new MemoryStream()) {
using (var book = new ClosedXML.Excel.XLWorkbook()) {
var sheet = book.Worksheets.Add("sample");
ICollection<T> rows = type == typeof(IQueryable<T>) ? ((IQueryable<T>)value).ToList() : new List<T>() { (T)value };
for (var r = 0; r < rows.Count; r++) {
var result = builder((T)rows.ElementAt(r));
for (var c = 0; c < result.Count(); c++) {
if (result.ElementAt(c) != null)
sheet.Cell(r + 2, c + 1).Value = result.ElementAt(c).Value.ToString();
}
}
sheet.Columns().AdjustToContents();
book.SaveAs(ms);
byte[] buffer = new byte[ms.Length];
ms.Position = 0;
ms.Read(buffer, 0, buffer.Length);
writeStream.Write(buffer, 0, buffer.Length);
}
}
}
CanWriteType will return false, because T is CustomClassification and type is Classification. Your formatter will not be used if it returns false.
Because a Classification is not necessarily a CustomClassification this can't work.
To achieve what you want, you need to change your implementation a bit.
Your ExcelMediaTypeFormatter will no longer be generic. And it will not get passed one Func but a list of IRowsBuilder instances. These will be used in the WriteToStream method to select the correct builder:
public interface IRowsBuilder
{
bool CanBuildFor(Type type);
IEnumerable<Type> SupportedTypes { get; }
ExcelRow BuildRow(object value);
}
public class RowsBuilder<T> : IRowsBuilder where T : Classification
{
Func<T, ExcelRow> _builder;
public RowsBuilder(Func<T, ExcelRow> builder)
{
if(builder == null) throw new ArgumentNullException("builder");
_builder = builder;
}
public bool CanBuildFor(Type type)
{
return type.IsSubclassOf(typeof(T));
}
public IEnumerable<Type> SupportedTypes
{
get { yield return typeof(T); }
}
public ExcelRow BuildRow(object value)
{
if(!CanBuildFor(value.GetType()))
throw new ArgumentException();
return _builder((T)value);
}
}
public class ExcelMediaTypeFormatter : BufferedMediaTypeFormatter
{
private readonly ILookup<Type, IRowsBuilder> _builder;
public ExcelMediaTypeFormatter(IEnumerable<IRowsBuilder> builder)
{
_builder = builder.SelectMany(x => builder.SupportedTypes
.Select(y => new
{
Type = y,
Builder = x
}))
.ToLookup(x => x.Type, x => x.Builder);
}
public override bool CanWriteType(Type type)
{
return type == typeof(IQueryable<Classification>) ||
type == typeof(Classification);
}
// ...
public override void WriteToStream(Type type, object value,
Stream writeStream, HttpContent content)
{
// ...
List<Classification> rows;
if(type == typeof(IQueryable<Classification>))
rows = ((IQueryable<Classification>)value).ToList();
else
rows = new List<Classification> { (Classification)value };
for (var r = 0; r < rows.Count; r++)
{
var value = rows.ElementAt(r);
var builder = _builder[value.GetType()];
var result = builder(value);
// ...
}
}
}
You would now only register one ExcelMediaTypeFormatter with all the builders:
var customBuilder = new RowsBuilder<CustomClassification>(
t => new ExcelRow(ExcelCell.Map(t.ChemicalAbstractService),
ExcelCell.Map(t.Substance),
ExcelCell.Map(t.Columns("Classifidcation")),
ExcelCell.Map(t.Columns("Classification"))));
var builders = new List<IRowsBuilder>();
builder.Add(customBuilder);
builder.Add(someOtherBuilder);
var excelFormatter = new ExcelMediaTypeFormatter(builders, format => "excel");
GlobalConfiguration.Configuration
.Formatters
.Add(excelFormatter);

Categories