I am creating a custom attribute. And I will use it in multiple classes:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class Display : System.Attribute
{
public string Name { get; set; }
public string Internal { get; set; }
}
public class Class1
{
[Display(Name = "ID")]
public int ID { get; set; }
[Display(Name = "Name")]
public string Title { get; set; }
}
public class Class2
{
[Display(Name = "ID")]
public int ID { get; set; }
[Display(Name = "Name")]
public string Title { get; set; }
}
Here is working correctly but I want to make it as generic as possible, like the MVC example:
Class1 class1 = new Class1();
class1.Title.DisplayName () / / returns the value "Name"
The only thing I could do was generate a loop of my property, but I need to inform my typeof Class1
foreach (var prop in typeof(Class1).GetProperties())
{
var attrs = (Display[])prop.GetCustomAttributes(typeof(Display), false);
foreach (var attr in attrs)
{
Console.WriteLine("{0}: {1}", prop.Name, attr.Name);
}
}
Is there any way to do it the way I want?
You can't do what you've shown exactly because class1.Title is an expression that evaluates to a string. You are looking for metadata about the Title property. You can use Expression Trees to let you write something close. Here's a short helper class that pulls the attribute value out of a property in an expression tree:
public static class PropertyHelper
{
public static string GetDisplayName<T>(Expression<Func<T, object>> propertyExpression)
{
Expression expression;
if (propertyExpression.Body.NodeType == ExpressionType.Convert)
{
expression = ((UnaryExpression)propertyExpression.Body).Operand;
}
else
{
expression = propertyExpression.Body;
}
if (expression.NodeType != ExpressionType.MemberAccess)
{
throw new ArgumentException("Must be a property expression.", "propertyExpression");
}
var me = (MemberExpression)expression;
var member = me.Member;
var att = member.GetCustomAttributes(typeof(DisplayAttribute), false).OfType<DisplayAttribute>().FirstOrDefault();
if (att != null)
{
return att.Name;
}
else
{
// No attribute found, just use the actual name.
return member.Name;
}
}
public static string GetDisplayName<T>(this T target, Expression<Func<T, object>> propertyExpression)
{
return GetDisplayName<T>(propertyExpression);
}
}
And here's some sample usage. Note that you really don't even need an instance to get this metadata, but I have included an extension method that may be convenient.
public static void Main(string[] args)
{
Class1 class1 = new Class1();
Console.WriteLine(class1.GetDisplayName(c => c.Title));
Console.WriteLine(PropertyHelper.GetDisplayName<Class1>(c => c.Title));
}
Related
Some of my actions accept models like:
public class PaymentRequest
{
public decimal Amount { get; set; }
public bool? SaveCard { get; set; }
public int? SmsCode { get; set; }
public BankCardDetails Card { get; set; }
}
public class BankCardDetails
{
public string Number { get; set; }
public string HolderName { get; set; }
public string ExpiryDate { get; set; }
public string ValidationCode { get; set; }
}
And the action method looks like:
[HttpPost]
[Route("api/v1/payment/pay")]
public Task<BankCardActionResponse> Pay([FromBody] PaymentRequest request)
{
if (request == null)
throw new HttpResponseException(HttpStatusCode.BadRequest);
return _paymentService.PayAsync(DataUserHelper.PhoneNumber, request);
}
I use Nlog. I think it's clear this is a bad idea to log all this bank data. My log config file contained the following line:
<attribute name="user-requestBody" layout="${aspnet-request-posted-body}"/>
I logged the request. I decided to refactor that and planned the following strategy. Actions that contain sensitive data into their requests I will mark with an attribute like
[RequestMethodFormatter(typeof(PaymentRequest))]
then take a look at my custom renderer:
[LayoutRenderer("http-request")]
public class NLogHttpRequestLayoutRenderer : AspNetRequestPostedBody
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
base.DoAppend(builder, logEvent);
var body = builder.ToString();
// Get attribute of the called action.
var type = ... // How can I get "PaymentRequest" from the [RequestMethodFormatter(typeof(PaymentRequest))]
var res = MaskHelper.GetMaskedJsonString(body, type);
// ... and so on
}
}
I think you understand the idea. I need the type from the method's RequestMethodFormatter attribute. Is it even possible to get it into the renderer? I need it because I'm going to deserialize request JSON into particular models (it's gonna be into the MaskHelper.GetMaskedJsonString), work with the models masking the data, serialize it back into JSON.
So, did I choose a wrong approach? Or it's possible to get the type from the attribute into the renderer?
After some research, I ended up with the following solution:
namespace ConsoleApp7
{
internal class Program
{
private static void Main()
{
var sourceJson = GetSourceJson();
var userInfo = JsonConvert.DeserializeObject(sourceJson, typeof(User));
Console.WriteLine("----- Serialize without Resolver-----");
Console.WriteLine(JsonConvert.SerializeObject(userInfo));
Console.WriteLine("----- Serialize with Resolver-----");
Console.WriteLine(JsonConvert.SerializeObject(userInfo, new JsonSerializerSettings
{
ContractResolver = new MaskPropertyResolver()
}));
}
private static string GetSourceJson()
{
var guid = Guid.Parse("3e92f0c4-55dc-474b-ae21-8b3dac1a0942");
return JsonConvert.SerializeObject(new User
{
UserId = guid,
Age = 19,
Name = "John",
BirthDate = new DateTime(1990, 5, 12),
Hobbies = new[]
{
new Hobby
{
Name = "Football",
Rating = 5,
DurationYears = 3,
},
new Hobby
{
Name = "Basketball",
Rating = 7,
DurationYears = 4,
}
}
});
}
}
public class User
{
[MaskGuidValue]
public Guid UserId { get; set; }
[MaskStringValue("***")] public string Name { get; set; }
public int Age { get; set; }
[MaskDateTimeValue]
public DateTime BirthDate { get; set; }
public Hobby[] Hobbies { get; set; }
}
public class Hobby
{
[MaskStringValue("----")]
public string Name { get; set; }
[MaskIntValue(replacement: 11111)]
public int Rating { get; set; }
public int DurationYears { get; set; }
}
public class MaskPropertyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = base.CreateProperties(type, memberSerialization);
var allowedPropertyTypes = new Type[]
{
typeof(Guid),
typeof(DateTime),
typeof(string),
typeof(int),
};
foreach (var prop in props.Where(p => allowedPropertyTypes.Contains(p.PropertyType)))
{
if (prop.UnderlyingName == null)
continue;
var propertyInfo = type.GetProperty(prop.UnderlyingName);
var attribute =
propertyInfo?.GetCustomAttributes().FirstOrDefault(x => x is IMaskAttribute) as IMaskAttribute;
if (attribute == null)
{
continue;
}
if (attribute.Type != propertyInfo.PropertyType)
{
// Log this case, cause somebody used wrong attribute
continue;
}
prop.ValueProvider = new MaskValueProvider(propertyInfo, attribute.Replacement, attribute.Type);
}
return props;
}
private class MaskValueProvider : IValueProvider
{
private readonly PropertyInfo _targetProperty;
private readonly object _replacement;
private readonly Type _type;
public MaskValueProvider(PropertyInfo targetProperty, object replacement, Type type)
{
_targetProperty = targetProperty;
_replacement = replacement;
_type = type;
}
public object GetValue(object target)
{
return _replacement;
}
public void SetValue(object target, object value)
{
_targetProperty.SetValue(target, value);
}
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskStringValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(string);
public object Replacement { get; }
public MaskStringValueAttribute(string replacement)
{
Replacement = replacement;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskIntValueAttribute : Attribute, IMaskAttribute
{
public object Replacement { get; }
public Type Type => typeof(int);
public MaskIntValueAttribute(int replacement)
{
Replacement = replacement;
}
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskGuidValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(Guid);
public object Replacement => Guid.Empty;
}
[AttributeUsage(AttributeTargets.Property)]
public class MaskDateTimeValueAttribute : Attribute, IMaskAttribute
{
public Type Type => typeof(DateTime);
public object Replacement => new DateTime(1970, 1, 1);
}
public interface IMaskAttribute
{
Type Type { get; }
object Replacement { get; }
}
}
I hope somebody will find it helpful.
You can try nuget package https://www.nuget.org/packages/Slin.Masking and https://www.nuget.org/packages/Slin.Masking.NLog.
It can easily be integrated with DotNet projects with slight changes, and you can define your rules for it. But the document needs some improvement.
As a suggestion, you can use two files:
masking.json (can be a generic one, that shared across all projects)
masking.custom.json (can be used with particular rules for specific projects)
I have a class Class1
public class Class1
{
public string ABC { get; set; }
public string DEF { get; set; }
public string GHI { get; set; }
public string JLK { get; set; }
}
How can I get a list of, in this case 'ABC', 'DEF', ...
I want to get the name of all public fields.
I tried the following:
Dictionary<string, string> props = new Dictionary<string, string>();
foreach (var prop in classType.GetType().GetProperties().Where(x => x.CanWrite == true).ToList())
{
//Console.WriteLine("{0}={1}", prop.Name, prop.GetValue(classitem, null));
//objectItem.SetValue("ABC", "Test");
props.Add(prop.Name, "");
}
And:
var bindingFlags = BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public;
var fieldValues = classType.GetType()
.GetFields(bindingFlags)
.Select(field => field.GetValue(classType))
.ToList();
But neither gave me the wanted results.
Thanks in advance
Try something like this:
using System;
using System.Linq;
public class Class1
{
public string ABC { get; set; }
public string DEF { get; set; }
public string GHI { get; set; }
public string JLK { get; set; }
}
class Program
{
static void Main()
{
// Do this if you know the type at compilation time
var propertyNames
= typeof(Class1).GetProperties().Select(x => x.Name);
// Do this if you have an instance of the type
var instance = new Class1();
var propertyNamesFromInstance
= instance.GetType().GetProperties().Select(x => x.Name);
}
}
Isn't clear what classType means in your original code.
If it's a instance of Class1 that should work; if you already got a System.Type, your classType.GetType() will not return your properties.
Also, you should be aware of properties and fields difference, as it matters to reflection. The code below lists your original properties, skips a property without setter and also a field, just to demonstrate how that conceptual difference affects your code.
class Program
{
static void Main(string[] args)
{
var classType = typeof (Class1);
foreach (var prop in classType.GetProperties().Where(p => p.CanWrite))
{
Console.WriteLine(prop.Name);
}
foreach (var field in classType.GetFields())
{
Console.WriteLine(field.Name);
}
}
}
public class Class1
{
public string ABC { get; set; }
public string DEF { get; set; }
public string GHI { get; set; }
public string JLK { get; set; }
public string CantWrite { get { return ""; } }
public string Field = "";
}
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.
I've the following attribute:
class HandlerAttribute : System.Attribute
{
public string MainName { get; private set; }
public string SubName { get; private set; }
public HandlerAttribute(string pValue, bool pIsMain) {
if (pIsMain) MainName = pValue;
else SubName = pValue;
}
}
And this is the way I use the attribute
[Handler("SomeMainName", true)]
class Class1 {
[Handler("SomeSubName", false)]
void HandleThis() {
Console.WriteLine("Hi");
}
}
What I want to achieve is that I import the MainName value from the parent class attribute into the method defined inside the class.
I hope that someone can help me with this :)
Thanks in advance
I would advice to write a helper method for this, basically it isn't possible by default because you don't have access to the attribute's target.
Code
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
sealed class HandlerAttribute : Attribute
{
public string MainName { get; private set; }
public string SubName { get; private set; }
public HandlerAttribute(string pValue, bool pIsMain) {
if (pIsMain) MainName = pValue;
else SubName = pValue;
}
public HandlerAttributeData GetData(MemberInfo info)
{
if (info is Type)
{
return new HandlerAttributeData(MainName, null);
}
HandlerAttribute attribute = (HandlerAttribute)info.DeclaringType.GetCustomAttributes(typeof(HandlerAttribute), true)[0];
return new HandlerAttributeData(attribute.MainName, SubName);
}
}
class HandlerAttributeData
{
public string MainName { get; private set; }
public string SubName { get; private set; }
public HandlerAttributeData(String mainName, String subName)
{
MainName = mainName;
SubName = subName;
}
}
Usage
HandlerAttribute att = (HandlerAttribute)typeof(App).GetCustomAttributes(typeof(HandlerAttribute), true)[0];
HandlerAttributeData data = att.GetData(typeof(App));
You can use it with any member and if you write a extension method you can make it even shorter.
Update:
You may use this extension method as well.
public static class AttributeExtensions
{
public static string GetMainName(this Class1 class1)
{
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(class1.GetType()); // reflection
foreach (System.Attribute attr in attrs)
{
if (attr is HandlerAttribute)
{
string mainName = (attr as HandlerAttribute).MainName;
return mainName;
}
}
throw new Exception("No MainName Attribute");
}
}
Now after coding the extension method you may use it on any Class1 type object;
Class1 c1 = new Class1();
string a =c1.GetMainName();
Or you may use it directly.
public class HandlerAttribute : System.Attribute
{
public string MainName { get; private set; }
public string SubName { get; private set; }
public HandlerAttribute(string pValue, bool pIsMain)
{
if (pIsMain) MainName = pValue;
else SubName = pValue;
}
}
[Handler("SomeMainName", true)]
class Class1
{
[Handler("SomeSubName", false)]
public void HandleThis()
{
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(this.GetType()); // reflection
foreach (System.Attribute attr in attrs)
{
if (attr is HandlerAttribute)
{
string mainName = (attr as HandlerAttribute).MainName;
}
}
}
}
Ok so at first I thought this was easy enough, and maybe it is and I'm just too tired - but here's what I'm trying to do. Say I have the following objects:
public class Container
{
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public List<Telephone> Telephones { get; set; }
}
public class Telephone
{
public string CellPhone { get; set; }
}
What I need to be able to do, is 'flatten' Containers property names in to a string (including ALL child properties AND child properties of child properties) that would look something like this:
Container.Name, Container.Addresses.AddressLine1, Container.Addresses.AddressLine2, Container.Addresses.Telephones.CellPhone
Does that make any sense? I can't seem to wrap it around my head.
I suggest you to mark all the classes, you need to grab, with custom attribute after that you could do something like this
class Program
{
static void Main(string[] args)
{
var lines = ExtractHelper.IterateProps(typeof(Container)).ToArray();
foreach (var line in lines)
Console.WriteLine(line);
Console.ReadLine();
}
}
static class ExtractHelper
{
public static IEnumerable<string> IterateProps(Type baseType)
{
return IteratePropsInner(baseType, baseType.Name);
}
private static IEnumerable<string> IteratePropsInner(Type baseType, string baseName)
{
var props = baseType.GetProperties();
foreach (var property in props)
{
var name = property.Name;
var type = ListArgumentOrSelf(property.PropertyType);
if (IsMarked(type))
foreach (var info in IteratePropsInner(type, name))
yield return string.Format("{0}.{1}", baseName, info);
else
yield return string.Format("{0}.{1}", baseName, property.Name);
}
}
static bool IsMarked(Type type)
{
return type.GetCustomAttributes(typeof(ExtractNameAttribute), true).Any();
}
public static Type ListArgumentOrSelf(Type type)
{
if (!type.IsGenericType)
return type;
if (type.GetGenericTypeDefinition() != typeof(List<>))
throw new Exception("Only List<T> are allowed");
return type.GetGenericArguments()[0];
}
}
[ExtractName]
public class Container
{
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
[ExtractName]
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public List<Telephone> Telephones { get; set; }
}
[ExtractName]
public class Telephone
{
public string CellPhone { get; set; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = true)]
public sealed class ExtractNameAttribute : Attribute
{ }
Per my comment, you could use something like this if it will always be a generic List type that you want to link to a child type. IteratePropertiesRecursively is an iterator over the properties of the given type, that will recursively enumerate the properties of the type and all child types linked through a generic List.
protected void Test()
{
Type t = typeof(Container);
string propertyList = string.Join(",", IteratePropertiesRecursively("", t).ToArray<string>());
// do something with propertyList
}
protected IEnumerable<string> IteratePropertiesRecursively(string prefix, Type t)
{
if (!string.IsNullOrEmpty(prefix) && !prefix.EndsWith(".")) prefix += ".";
prefix += t.Name + ".";
// enumerate the properties of the type
foreach (PropertyInfo p in t.GetProperties())
{
Type pt = p.PropertyType;
// if property is a generic list
if (pt.Name == "List`1")
{
Type genericType = pt.GetGenericArguments()[0];
// then enumerate the generic subtype
foreach (string propertyName in IteratePropertiesRecursively(prefix, genericType))
{
yield return propertyName;
}
}
else
{
// otherwise enumerate the property prepended with the prefix
yield return prefix + p.Name;
}
}
}
Note: This code will not correctly handle a type that recursively includes itself as a type of one of its properties. Trying to iterate over such a type will result in a StackOverflowException, as pointed out by #Dementic (thanks!).