When I add an "XmlAttribute/XmlElement" to a property in an Interface and 100 other classes inherit from this interface, when Serializing the object to XML - why doesn't the attributename I entered show up in the XML file after serialization?
interface Test
{
[XmlAttribute("Name")]
bool PropertyName { get; set; }
}
when saving the file, is shows "PropertyName" and not "Name".
Is there any way to make it work, so a proeprty with an XmlAttributes added to an interface changes the Value everywhere instead of Value because if a few classes inherit from the same interface it takes alot of time to add all those Attributes to all those classes that inherit it, and it would be easier to change the Name of the Attribute instead of changing a 100 of them.
Deukalin, spent some time and make it work with comprehensive tree of classes and interfaces.
Here is the working code
public interface ITestX
{
[XmlAttribute("NameX")]
string PropertyNameX { get; set; }
}
public interface ITestY
{
[XmlAttribute("NameY")]
string PropertyNameY { get; set; }
}
public interface ITestZ
{
[XmlAttribute("NameZ")]
string PropertyNameZ { get; set; }
}
public abstract class TestC : ITestZ
{
public abstract string PropertyNameZ { get; set; }
}
public abstract class TestA : TestC, ITestX, ITestY
{
public abstract string PropertyNameX { get; set; }
public abstract string PropertyNameY { get; set; }
}
public class TestB : TestA
{
public override string PropertyNameX { get; set; }
public override string PropertyNameY { get; set; }
public override string PropertyNameZ { get; set; }
}
public static class ClassHandler
{
public static T GetCustomAttribute<T>(this PropertyInfo propertyInfo, bool inherit) where T : Attribute
{
object[] attributes = propertyInfo.GetCustomAttributes(typeof(T), inherit);
return attributes == null || attributes.Length == 0 ? null : attributes[0] as T;
}
public static void GetXmlAttributeOverrides(XmlAttributeOverrides overrides, Type type)
{
if (type.BaseType != null)
{
GetXmlAttributeOverrides(overrides, type.BaseType);
}
foreach (Type derived in type.GetInterfaces())
{
foreach (PropertyInfo propertyInfo in derived.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = ClassHandler.GetCustomAttribute<XmlAttributeAttribute>(propertyInfo, true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null)
continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(type, propertyInfo.Name, attr1);
}
}
}
}
class Program
{
static void Main(string[] args)
{
XmlAttributeOverrides XmlAttributeOverrides = new XmlAttributeOverrides();
ClassHandler.GetXmlAttributeOverrides(XmlAttributeOverrides, typeof(TestB));
try
{
TestB xtest = new TestB() { PropertyNameX = "RajX", PropertyNameY = "RajY", PropertyNameZ = "RajZ" };
StringBuilder xmlString = new StringBuilder();
using (XmlWriter xtw = XmlTextWriter.Create(xmlString))
{
XmlSerializer serializer = new XmlSerializer(typeof(TestB), XmlAttributeOverrides);
serializer.Serialize(xtw, xtest);
xtw.Flush();
}
Console.WriteLine(xmlString.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Below is the output of sample above
<?xml version="1.0" encoding="utf-16"?><TestB xmlns:xsi="http://www.w3.org/2001/
XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" NameZ="RajZ" Na
meX="RajX" NameY="RajY" />
Press any key to continue . . .
I altered the code to suit my project more. What I did was this:
public static XmlAttributeOverrides GetXmlAttributeOverrides(Type type)
{
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
foreach (Type derived in ClassHandler.GetImplementedInterfaces(type))
{
foreach (PropertyInfo propertyInfo in derived.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = ClassHandler.GetCustomAttribute<XmlAttributeAttribute>(propertyInfo, true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null) continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(type, propertyInfo.Name, attr1);
}
}
return overrides;
}
The object I'm trying on which implements an inteface with properties all have "[XmlAttributeAttribute(SomeName)] over them.
Still, when I serialize it gives the same results. I don't get the Attribute value from the interface.
This is how I serialize:
public static void SerializeFile(String filename, object obj, bool deleteIfExists = true)
{
if (deleteIfExists)
{
FileManager.DeleteFile(filename);
}
Type[] extraTypes = ClassHandler.GetPropertiesTypes(obj, true);
using (var stream = new FileStream(filename, FileMode.Create))
{
//XmlSerializer xmlSerialize = new XmlSerializer(obj.GetType(), extraTypes);
XmlSerializer xmlSerialize = new XmlSerializer(obj.GetType(), GetXmlAttributeOverrides(obj.GetType()), extraTypes, null, null);
xmlSerialize.Serialize(stream, obj);
}
}
the two methods I use from my ClassHandler class:
public static T GetCustomAttribute<T>(this PropertyInfo propertyInfo, bool inherit) where T : Attribute
{
object[] attributes = propertyInfo.GetCustomAttributes(typeof(T), inherit);
return attributes == null || attributes.Length == 0 ? null : attributes[0] as T;
}
public static List<Type> GetImplementedInterfaces(Type type)
{
Type[] types = type.GetInterfaces();
List<Type> lTypes = new List<Type>();
foreach(Type t in types)
{
lTypes.Add(t);
}
return lTypes;
}
classes have the following structure:
interface IAnimal
{
// Properties
// Methods
}
public abstract class Animal : IAnimal
{
// Implements IAnimal properties and methods
// This XmlElement gets written correctly when XML Serializing
// Example:
[XmlElement("AnimalAge")]
public double Age
{
get { return _age; }
set { _age = value; }
}
}
public abstract class Bird : Animal, IAttributeWings
{
// Implements Attributes common for all "Birds"
// Setting "override" here gives me error
public bool HasWings { get { return _hasWings; } set { _hasWings = value; } }
}
public class Pelican : Bird, IAttributeCanFly
{
// Implements Attributes common for all "Pelicans"
// Does not implement previous attribute IAttributeWings since Bird class does this
// Setting "override" here gives me error as well
public bool CanFly { get { return _canFly; } set { _canFly = value; } }
}
and then the attribute interfaces only have properties like "bool CanFly, bool hasWings" and such and other attributes for a specific category like in this example.
Some reason it does not work in .NET... But I come up with this solution to overcome the problem you have using XmlAttributeOverrides. For more information about it, please visit the below link
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlattributeoverrides.aspx
You can optimize it by refactoring / caching this GetXmlAttributeOverrides() method somewhere in your application. Hope it helps.
public interface ITest
{
[XmlAttribute("Name")]
string PropertyName { get; set; }
}
public class XTest : ITest
{
public string PropertyName
{
get;
set;
}
}
public class Program
{
static void Main(string[] args)
{
try
{
XTest xtest = new XTest() { PropertyName = "Raj" };
StringBuilder xmlString = new StringBuilder();
using (XmlWriter xtw = XmlTextWriter.Create(xmlString))
{
XmlSerializer serializer = new XmlSerializer(typeof(XTest), GetXmlAttributeOverrides(typeof(XTest)));
serializer.Serialize(xtw, xtest);
xtw.Flush();
}
Console.WriteLine( xmlString.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static XmlAttributeOverrides GetXmlAttributeOverrides(Type derivedType)
{
Type type = typeof(ITest);
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attr = new XmlAttributes();
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = propertyInfo.GetCustomAttribute(typeof(XmlAttributeAttribute), true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null) continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(derivedType, propertyInfo.Name, attr1);
}
return overrides;
}
}
EDIT: Include this extension class in your application
public static class PropertyInfoEx
{
public static T GetCustomAttribute<T>(this PropertyInfo propertyInfo, bool inherit) where T : Attribute
{
object[] attributes = propertyInfo.GetCustomAttributes(typeof(T), inherit);
return attributes == null || attributes.Length == 0 ? null : attributes[0] as T;
}
}
Found some issues in your code. Below is the corrected code
public static XmlAttributeOverrides GetXmlAttributeOverrides(Type type)
{
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
// Get all interfaces that the "type" implements (is it same as "derivedType" from previously)?
foreach (Type derived in ClassHandler.GetImplementedInterfaces(type))
{
foreach (PropertyInfo propertyInfo in derived.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = ClassHandler.GetCustomAttribute<XmlAttributeAttribute>(propertyInfo, true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null) continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(type, propertyInfo.Name, attr1);
}
}
return overrides;
}
Try for yourself. Let me know if it works.
Related
I need to serialize a class so that the serialization will include all nested value types properties.
I found it somewhat hard to generalize it in English (Not a native speaker, so an edit to the wording is welcomed), so I'll explain:
If the property is of a value type - serialize its name and value
If the property is of a Nullable type: If its value is non-null, do as above (Effectively, serialize the Nullable's Value property); else, don't serialize it.
If the property is of a class type, serialize the class' properties according to the above, and do not serialize the class name.
For example, this:
public class SerializeMe
{
public int A { get; set; }
public int? B { get; set; }
public int? C { get; set; }
public MyClass MyClass { get; set;}
}
public class MyClass
{
public int Z { get; set;}
}
If instantiated like this:
public static void Main()
{
var instance = new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2},
});
}
Should be serialized like this:
<SerializeMe>
<A>1</A>
<B>3</B>
<Z>2</Z>
</SerializeMe>
But I don't know how to do the last bullet, and I end with:
<SerializeMe>
<A>1</A>
<B>3</B>
<UndesiredTag><Z>2</Z></UndesiredTag>
</SerializeMe>
Now, the last bullet requirement invites recursion, but as I understand from this answer, it's the parent class' WriteXml that may be able to omit the <UndesiredTag> tag, while the nested class can't.
So, what I currently have (fiddle):
using System;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.IO;
public class SerializeMe : IXmlSerializable
{
public int A { get; set; }
public int? B { get; set; }
public int? C { get; set; }
public MyClass MyClass { get; set;}
public void WriteXml(XmlWriter writer)
{
Program.WriteXml<SerializeMe>(writer, this);
}
public void ReadXml(XmlReader reader) {}
public XmlSchema GetSchema() { return null; }
}
[AttributeUsage(AttributeTargets.Class)]
public class Nested : Attribute
{}
[Nested]
public class MyClass : IXmlSerializable
{
public int Z { get; set;}
public void WriteXml(XmlWriter writer)
{
Program.WriteXml<MyClass>(writer, this);
}
public void ReadXml(XmlReader reader) {}
public XmlSchema GetSchema() { return null; }
}
public class Program
{
public static void Main()
{
var s = XmlSerialize<SerializeMe>(new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2},
});
Console.WriteLine(s);
}
public static string XmlSerialize<T>(T entity) where T : class
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
XmlSerializer xsSubmit = new XmlSerializer(typeof(T));
StringWriter sw = new StringWriter();
using (XmlWriter writer = XmlWriter.Create(sw, settings))
{
var xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
xsSubmit.Serialize(writer, entity, xmlns);
return sw.ToString();
}
}
public static void WriteXml<T>(XmlWriter writer, T obj)
{
PropertyInfo[] props = obj.GetType().GetProperties();
foreach (var prop in props)
{
var val = prop.GetValue(obj);
if (val != null)
{
if (prop.PropertyType.IsValueType ||
prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
writer.WriteElementString(prop.Name, val.ToString());
}
else
{
if (prop.PropertyType.GetCustomAttribute(typeof(Nested)) != null)
{
writer.WriteStartElement("UndesiredTag"); // If only I could use an empty string...
((dynamic)val).WriteXml(writer);
writer.WriteEndElement();
}
}
}
}
}
}
Note that my current code assumes only one level of nesting. If you think you can solve my issue using a recursion, it would be better - since you'd allow multiple nesting levels.
Since you override all default serialization anyway, it seems simpler to me to just ditch XmlSerializer and do it completely on your own.
public static void Main()
{
var s = XmlSerialize(new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2 },
});
Console.WriteLine(s);
}
public static string XmlSerialize(object entity)
{
var buf = new StringBuilder();
using (var writer = XmlWriter.Create(buf, new XmlWriterSettings() {
OmitXmlDeclaration = true,
Indent = true
}))
{
WriteElement(writer, entity);
}
return buf.ToString();
}
static void WriteElement(XmlWriter writer, object obj)
{
writer.WriteStartElement(obj.GetType().Name);
WriteElementProperties(writer, obj);
writer.WriteEndElement();
}
static void WriteElementProperties(XmlWriter writer, object obj)
{
foreach (var prop in obj.GetType().GetProperties())
{
var val = prop.GetValue(obj);
if (val != null)
{
if (prop.PropertyType.IsValueType ||
prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
writer.WriteElementString(prop.Name, val.ToString());
}
else
{
if (prop.PropertyType.GetCustomAttribute(typeof(Nested)) != null)
{
WriteElementProperties(writer, val);
} else {
WriteElement(writer, val);
}
}
}
}
}
I want to convert an object A to object B. The classes A and B have the same properties, just the names are changed.
I use this method:
/// <summary>
internal static T objectMapper<T>(object objectSource, T objectTarget)
{
dynamic o = objectSource;
Type typeA = objectSource.GetType();
Type typeB = objectTarget.GetType();
IList<PropertyInfo> propsA = new List<PropertyInfo>(typeA.GetProperties());
IList<PropertyInfo> propsB = new List<PropertyInfo>(typeB.GetProperties());
dynamic s;
ArrayList listArray = new ArrayList();
foreach (var prop in propsA)
{
s = objectSource.GetType().GetProperty(prop.Name).GetValue(objectSource, null);
listArray.Add(s);
}
int i = 0;
foreach (var prop in propsB)
{
prop.SetValue(objectTarget, listArray[i], null);
i++;
}
return objectTarget;
}
How can I edit properties of objectB in the foreach loop? I want to use a generic method for different objects.
This solution provides both your reflection-way and an alternative way by defining and implementing a copy method CopyFrom. To reduce code you could make the interface a base-class so you don't need to implement CopyFrom in the sub-classes....
public interface MyInterface
{
int Prop1 { get; set; }
string Prop2 { get; set; }
void CopyFrom(MyInterface obj);
}
public class A: MyInterface
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public void CopyFrom(MyInterface obj)
{
this.Prop1 = obj.Prop1;
this.Prop2 = obj.Prop2;
}
}
public class B: MyInterface
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public void CopyFrom(MyInterface obj)
{
this.Prop1 = obj.Prop1;
this.Prop2 = obj.Prop2;
}
}
public static class CopyUtils
{
public static void Copy(MyInterface src, MyInterface dst)
{
var props = typeof(MyInterface).GetProperties();
foreach(var prop in props)
{
prop.SetValue(dst, prop.GetValue(src, null), null);
}
}
}
I feel there might be a deeper architecture issue here. I'm failing to imagine why would you want to "copy" the values of the properties from one object of a class to another of a different class with the same property names.
If you're trying to "shape" the object maybe just passing an interface will do the work
Anyhow, see if this helps:
public static class ObjectMorpher
{
public class InvalidMorphException : Exception
{
}
[AttributeUsage(AttributeTargets.Property)]
public class IgnoredOnMorphAttribute : Attribute
{
}
public static TargetType Morph<TargetType>(this object source, TargetType dest, Func<string, string> propertyMatcher = null, bool failOnNoMatch = false)
where TargetType : class
{
if (source == null || dest == null)
throw new ArgumentNullException();
foreach (var sourceProp in source.GetType().GetProperties().Where(x => x.GetCustomAttributes<IgnoredOnMorphAttribute>().Any() == false))
{
var destProp = typeof(TargetType).GetProperties().Where(x => x.Name == ((propertyMatcher == null) ? sourceProp.Name : propertyMatcher(sourceProp.Name))).FirstOrDefault();
//check property exists
if (destProp == null)
{
if (failOnNoMatch)
throw new InvalidMorphException();
else
continue;
}
//check value type is assignable
if (!destProp.GetType().IsAssignableFrom(sourceProp.GetType()))
{
if (failOnNoMatch)
throw new InvalidMorphException();
else
continue;
}
destProp.SetValue(dest, sourceProp.GetValue(source));
}
return dest;
}
}
Usage example:
var A = new ClassA();
var B = new ClassB();
B = A.Morph(B);
Optionally you can set a property match for the case when properties doesn't have the exact same name.
Also notice the use of the IgnoredOnMorph attribute to mark properties as not morph-able (like calculated properties)
You might find automapper of use here (see https://github.com/AutoMapper/AutoMapper/wiki/Getting-started).
You would need to create a line for each object mapping in a startup file to set it up but if the properties are the same this would be as simple as:
mapper.CreateMap<ClassA, ClassB>().ReverseMap();
And then a single line to resolve the mapping when needed
mapper.Map(objectOfClassA, new ClassB());
If I have something like:
public abstract class Animal { }
[XmlRoot]
public class Dog:Animal { }
[XmlRoot]
Public class Cat:Animal { }
How do I go about serializing (and then be able to de-serialize) a Cat object like:
<Cat> </Cat>
and not something like:
<Animal type="Cat"> </Animal>
I've tried using different combinations from the System.Xml.Serialization and System.Runtime.Serialization namespaces, but I can't seem to get it.
I can get a serialized object to look the way I want by specifying the type of the object in the serializer. This works for serialization, but not for de-serialization..because I don't know the type of object in the xml.
One possible solution would be:
public abstract class Animal
{
static Dictionary<String,Type> typeDic;
static Animal()
{
typeDic = new Dictionary<string, Type>();
//Get classes in the same namespace of this object
Type ctype = typeof(Animal);
Type[] types = ctype.Assembly.GetTypes().Where(t => String.Equals(t.Namespace, ctype.Namespace, StringComparison.Ordinal)).ToArray();
//For any XmlRootAttribute objects, add the ElementName and Type to a dictionary
foreach(Type type in types)
{
foreach (XmlRootAttribute xmlRoot in type.GetCustomAttributes(typeof(XmlRootAttribute), false))
{
typeDic.Add(xmlRoot.ElementName, type);
}
}
}
public static Content read(String XML)
{
XmlSerializer s = null;
//check the first element to see what the name is, then create the serializer using the type from the dictionary
XmlReader reader = XmlReader.Create(GenerateStreamFromString(XML));
reader.Read();
if (reader.Name == "xml")
{
while (reader.MoveToContent() != XmlNodeType.Element) { }
}
if (typeDic.ContainsKey(reader.Name))
s = new XmlSerializer(typeDic[reader.Name]);
else
throw new Exception("Unknown Type in read");
return (Content)s.Deserialize(reader);
}
public static string write<T>(T f)
{
XmlSerializer s = new XmlSerializer(typeof(T));
MemoryStream stream = new MemoryStream();
s.Serialize(stream, f);
stream.Position = 0;
return StreamToString(stream);
}
}
[XmlRoot(ElementName="Dog")]
public class Dog:Animal { }
[XmlRoot(ElementName="Cat")]
Public class Cat:Animal { }
I tried this for a long time and could not figure it out either. the only thing i could come up with is using some reflection and generating the doc myself:
class Program
{
static void Main(string[] args)
{
List<Animal> animals = new List<Animal>
{
new Dog{Name = "Ernie", HasFleas = true},
new Cat{ Name = "Bert", Collar = "Blue with a little bell" }
};
XDocument doc = new XDocument();
doc.Declaration = new XDeclaration("1.0","utf-8","true");
doc.Add(new XElement("root", animals.Select(animal => animal.GetElement)));
Console.WriteLine(doc);
}
}
public abstract class Animal
{
[XmlAttribute]
public string Name { get; set; }
public XElement GetElement
{
get
{
Type type = this.GetType();
XElement result = new XElement(type.Name);
foreach (PropertyInfo property in
type.GetProperties().Where(pi=> pi.CustomAttributes.Any(ca=> ca.AttributeType == typeof(System.Xml.Serialization.XmlAttributeAttribute))))
{
result.Add(new XAttribute(property.Name,property.GetValue(this)));
}
foreach (PropertyInfo property in
type.GetProperties().Where(pi => pi.CustomAttributes.Any(ca => ca.AttributeType == typeof(System.Xml.Serialization.XmlElementAttribute))))
{
result.Add(new XElement(property.Name,property.GetValue(this)));
}
return result;
}
}
}
public class Dog : Animal
{
[XmlAttribute]
public bool HasFleas { get; set; }
}
public class Cat : Animal
{
[XmlElement]
public string Collar { get; set; }
}
To deserialize you have to do the other way round so some form of factory.
I need to set a property inside of a class from another class that defines the first class as a property. I want to default a value inside the child class. An example of this would be:
public enum NamingConvention
{
Name1 = 1,
Name2
}
public class Class1
{
public Class1()
{
}
public int Id { get; set; }
public NamingConvention Naming{ get; set; }
}
public class Class2
{
public Class2()
{
}
public List<Class1> Name1s { get; set; }
}
public class Class3
{
public Class2()
{
}
public List<Class1> Name2s { get; set; }
}
I want to be able to put an attribute or something over the Class1 property inside of Class2 and Class3 so that in Class2, the Naming Property gets set to Name1 and and for Class3, it would be automatically set to Name2.
Hope that makes sense. I tried to make this as simple an example as possible. Any ideas out there? I am trying to avoid abstract classes because my real entities are tied to nHibernate and don't want to change the model at this time.
This can be accomplished with the use of the DefaultValueAttribute, a custom TypeConverter and Reflection. It seems unlikely this will perform better than what you are currently doing, but I'll leave that for you to evaluate.
Apply the TypeConverter attribute to Class 1
[TypeConverter(typeof(Class1Converter))]
public class Class1
{
public int Id { get; set; }
public NamingConvention Naming { get; set; }
}
public enum NamingConvention
{
Name1 = 1,
Name2,
Name3,
Name4
}
Define the Class1Converter. Note this simple converter only sets the value of the NamingConvention parameter.
public class Class1Converter: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context,
Type destinationType)
{
if(destinationType == typeof(Class1))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value)
{
var stringValue = value as string;
if(stringValue != null)
{
return new Class1
{
Naming = (NamingConvention)Enum.Parse(typeof(NamingConvention), stringValue)
};
}
return base.ConvertFrom(context, culture, value);
}
}
For convenience I am declaring this in an Extension Method, it could easily be set up as part of the classes with defaults...
public static class DefaultExtension
{
public static IEnumerable<PropertyInfo> GetProperties<T>(this Type type)
{
return type.GetProperties().Where(p => p.PropertyType == typeof (T));
}
public static void SetDefaults<T>(this T toDefault)
{
foreach (PropertyInfo p in toDefault.GetType().GetProperties())
{
foreach (var dv in p.GetCustomAttributes(true).OfType<DefaultValueAttribute>())
{
p.SetValue(toDefault, dv.Value, null);
}
}
}
}
Finally you declare place DefaultValue attributes on your properties. I am calling SetDefaults() from the constructors here for convenience again, in your case you would still need to call it after the instances are loaded from NHibernate.
public class Class2
{
public int X { get; set; }
[DefaultValue(typeof(Class1), "Name2")]
public Class1 Name2Class { get; set; }
public Class2()
{
this.SetDefaults();
}
}
public class Class3
{
public int Y { get; set; }
[DefaultValue(typeof(Class1), "Name3")]
public Class1 Name3Class { get; set; }
public Class3()
{
this.SetDefaults();
}
}
Unit test demonstrating validity...
[Test]
public void TestDefaultValueAttribute()
{
//Class2 have Name2 as the default value for the Naming property
var c2 = new Class2();
Assert.That(c2,Is.Not.Null);
Assert.That(c2.Name2Class, Is.Not.Null);
Assert.That(c2.Name2Class.Naming, Is.EqualTo(NamingConvention.Name2));
//Class3 have Name3 as the default value for the Naming Property
var c3 = new Class3();
Assert.That(c3, Is.Not.Null);
Assert.That(c3.Name3Class, Is.Not.Null);
Assert.That(c3.Name3Class.Naming, Is.EqualTo(NamingConvention.Name3));
//wipes out other properties of the Class1 attribute.
// to demonstrate, set properties to something other than the default then call
// SetDefaults again.
c3.Name3Class.Naming = NamingConvention.Name1;
c3.Name3Class.Id = 10;
c3.SetDefaults();
Assert.That(c3.Name3Class.Id, Is.EqualTo(0));
Assert.That(c3.Name3Class.Naming, Is.EqualTo(NamingConvention.Name3));
}
You will notice that this wipes out the Id property of Class1 If this is not desired, you could come up with a more targeted version of SetDefaults that only overwrote specific properties of Class1. At this point I don't know if I would really continue using DefaultValue, as use case deviates from the original and using this in combination with the above method would produce unexpected results. I would probably write a custom 'DefaultNaminingConventionAttribute for this purpose.
public static void SetDefaultNamingConvention<T>(this T toDefault)
{
foreach (PropertyInfo p in toDefault.GetType().GetProperties<Class1>())
{
foreach (var dv in p.GetCustomAttributes(true).OfType<DefaultValueAttribute>())
{
var pValue = p.GetValue(toDefault, null) as Class1;
if (pValue != null)
{
pValue.Naming = ((Class1)dv.Value).Naming;
}
else
{
p.SetValue(toDefault, dv.Value, null);
}
}
}
}
[Test]
public void SetDefaultNamingConventionDefaultShouldOnlyDefaultNamingProperty()
{
var c3 = new Class3();
c3.Name3Class.Naming = NamingConvention.Name1;
c3.Name3Class.Id = 20;
c3.SetDefaultNamingConvention();
Assert.That(c3.Name3Class.Id, Is.EqualTo(20));
Assert.That(c3.Name3Class.Naming, Is.EqualTo(NamingConvention.Name3));
}
EDIT: Updated to deal with setting defaults for list members
With this new SetListDefaults extension method, we now can apply the default to members of List<Class1>. Here I would almost definitely no longer use DefaultValue, but would define a custom attribute for use with collections. This is beyond the scope of the question though.
public static class DefaultExtension
{
public static IEnumerable<PropertyInfo> GetProperties<T>(this Type type)
{
return type.GetProperties().Where(p => p.PropertyType == typeof (T));
}
public static void SetListDefaults<T>(this T toDefault)
{
foreach (PropertyInfo p in toDefault.GetType().GetProperties<List<Class1>>())
{
foreach (var dv in p.GetCustomAttributes(true).OfType<DefaultValueAttribute>())
{
var pValue = p.GetValue(toDefault, null) as List<Class1>;
if (pValue != null)
{
foreach (var class1 in pValue)
{
class1.Naming = ((Class1) dv.Value).Naming;
}
}
}
}
}
}
Now provided a class with a List property...
public class Class4
{
public int Z { get; set; }
[DefaultValue(typeof (Class1), "Name4")]
public List<Class1> Name4Classes { get; set; }
}
And a unit test to verify only the Naming Property of each item in the list is modified.
[Test]
public void SetListDefaultsShouldResetNamingConventionOfEachListMember()
{
var c4 = new Class4
{
Z = 100,
Name4Classes = new List<Class1>
{
new Class1 {Id = 1, Naming = NamingConvention.Name1},
new Class1 {Id = 2, Naming = NamingConvention.Name2},
new Class1 {Id = 3, Naming = NamingConvention.Name3}
}
};
Assert.That(c4.Name4Classes, Is.Not.Empty);
Assert.That(c4.Name4Classes.Count, Is.EqualTo(3));
Assert.That(c4.Name4Classes.Any(c => c.Id == 0), Is.False);
Assert.That(c4.Name4Classes.Any(c => c.Naming == NamingConvention.Name4), Is.False);
c4.SetListDefaults();
Assert.That(c4.Name4Classes, Is.Not.Empty);
Assert.That(c4.Name4Classes.Count, Is.EqualTo(3));
Assert.That(c4.Name4Classes.Any(c=> c.Id == 0), Is.False);
Assert.That(c4.Name4Classes.All(c=> c.Naming == NamingConvention.Name4), Is.True);
}
I would use the constructors.
In Class2's constructor:
public Class2()
{
Name1Class = new Class1()
Name1Class.Naming = NamingConvention.Name1
}
In Class3's Constructor:
public Class3()
{
Name2Class = new Class1()
Name2Class.Naming = NamingConvention.Name2
}
If you want to get fancy you could put a parameter on the constructor in Class1 to allow you to set Naming when the object is created.
So, if i have:
public class Sedan : Car
{
/// ...
}
public class Car : Vehicle, ITurn
{
[MyCustomAttribute(1)]
public int TurningRadius { get; set; }
}
public abstract class Vehicle : ITurn
{
[MyCustomAttribute(2)]
public int TurningRadius { get; set; }
}
public interface ITurn
{
[MyCustomAttribute(3)]
int TurningRadius { get; set; }
}
What magic can I use to do something like:
[Test]
public void Should_Use_Magic_To_Get_CustomAttributes_From_Ancestry()
{
var property = typeof(Sedan).GetProperty("TurningRadius");
var attributes = SomeMagic(property);
Assert.AreEqual(attributes.Count, 3);
}
Both
property.GetCustomAttributes(true);
And
Attribute.GetCustomAttributes(property, true);
Only return 1 attribute. The instance is the one built with MyCustomAttribute(1). This doesn't seem to work as expected.
object[] SomeMagic (PropertyInfo property)
{
return property.GetCustomAttributes(true);
}
UPDATE:
Since my above answer doesn't work why not to try something like this:
public void Should_Use_Magic_To_Get_CustomAttributes_From_Ancestry()
{
Assert.AreEqual(checkAttributeCount (typeof (Sedan), "TurningRadious"), 3);
}
int checkAttributeCount (Type type, string propertyName)
{
var attributesCount = 0;
attributesCount += countAttributes (type, propertyName);
while (type.BaseType != null)
{
type = type.BaseType;
attributesCount += countAttributes (type, propertyName);
}
foreach (var i in type.GetInterfaces ())
attributesCount += countAttributes (type, propertyName);
return attributesCount;
}
int countAttributes (Type t, string propertyName)
{
var property = t.GetProperty (propertyName);
if (property == null)
return 0;
return (property.GetCustomAttributes (false).Length);
}
this is a framework issue. Interface attributes are ignored by GetCustomAttributes. see the comment on this blog post http://hyperthink.net/blog/getcustomattributes-gotcha/#comment-65