How to access inherited attributes of a property in a partial class? - c#

I have the following classes:
interface IEntityWithId
{
int id { get; set; }
}
public class MyEntityMetaData
{
[Display(Name="Name of my property")]
public string MyProp { get; set; }
}
public partial class MyEntity : MyEntityMetaData, IEntityWithId
{
}
/* Autogenerated code somewhere else */
public partial class MyEntity
{
public int Id { get; set; }
public string MyProp { get; set; }
}
Now I have a IQueryable<IEntityWithId> in which the objects are actually of type MyEntity.
I used this code to extract display names:
private static List<string> GetColumnNames<T>(IQueryable<T> query) where T : class
{
Type elementType = query.ElementType;
var defaultColumns = new List<WebGridColumn>();
var columnNames =
(
from
p in elementType.GetProperties()
where
IsBindableType(p.PropertyType) &&
(p.GetIndexParameters().Length == 0)
select
new
{
DisplayName = (DisplayNameAttribute)p.GetCustomAttributes(typeof(DisplayNameAttribute), true).FirstOrDefault(),
Display = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault(),
Name = p.Name
}
).Select
(
name =>
name.DisplayName != null ?
name.DisplayName.DisplayName :
name.Display != null ?
name.Display.Name :
name.Name
).ToList();
return columnNames;
}
But it doesn't actually work (display names are null). I do get correct type and properties within the method, but the attributes are missing. I'm guessing it has something to do with the fact that those are attributes of a parent class and/or my class being a partial one.
Is there something I can do to actually get those attributes inside my method (I can't really change my input type)?
Edit: Having run into the problem yet again, I believe that the answer to this question is in the part of MVC code responsible for bringing ViewData.ModelMetadata to life (because ViewData.ModelMetadata has correct those display names, even when they are as oddly inherited as in this question). This post makes me believe that this part of MVC that I'm looking for is a ModelMetadataProvider. One of those days I'll have to analyze the code...

The autogenerated code is hiding MyEntityData.MyProp with MyEntity.Prop. MyEntity.Prop is to all efects a completely new method and does not inherit any attributes that might be defined in MyEntityData.MyProp.
If possible, you should refactor MyEntityData to:
public class MyEntityMetaData
{
[Display(Name="Name of my property")]
public virtual string MyProp { get; set; }
}
And make the code generation tool override said method:
/* Autogenerated code somewhere else */
public partial class MyEntity
{
public int Id { get; set; }
public override string MyProp { get; set; }
}

Related

Binding C# BindingList<T> to a DataGridView

Maybe the question is a little bit dumb, but I did not quite find solution anywhere else.
So I am using a BindingList of custom made class objects as a DataSource for DataGridView.
Everything works fine with properties, that are directly inherited from other classes, but if I have an object of other class in the main class, its properties wont show up in DataGridView.
Classes are:
enum Valsts
{
Latvija,
Igaunija,
Ķīna,
ASV
}
class Razotajs
{
public Valsts valsts { get; set; }
public string razotajaNosaukums { get; set; }
}
class Tehnika
{
public string krasa { get; set; }
public Razotajs razotajs = new Razotajs();
}
class Viedierice : Tehnika
{
public string operetajsistema { get; set; }
public double ekranaIzmers { get; set; }
public bool irHDMI { get; set; }
}
class MobilaisTelefons : Viedierice
{
public string modelis { get; set; }
public double svars { get; set; }
public SimKarte sim = new SimKarte();
public override string ToString()
{
return String.Join(";", modelis.ToString(),svars.ToString(),sim.veids.ToString(),operetajsistema.ToString(),ekranaIzmers.ToString(),irHDMI.ToString(),krasa.ToString(),razotajs.razotajaNosaukums.ToString(),
sim.numurs.ToString(),razotajs.valsts.ToString());
}
}
class SimKarte
{
public string veids { get; set;}
public int numurs { get; set; }
}
For example- I can see columns "modelis" and "svars", but attributes like "veids" and "numurs" from class SimKarte are not included in the DataGridView.
Is there any solution for this?
I've tried to add { get; set; } after declaring a new instance of an object in the class, but it's not even a real thing. I really don't have any idea, what would help me to solve this.
Thank you all in advance! :)
Honestly, I think the simplest solution is the one JohnG proposed; add proxy properties to your main class that read/write the properties of the complex objects
A datagridview will show only the simple types it knows how to show, from the top level class. It will not dig into properties of properties (otherwise even adding a string column would cause the grid to fill up with a Length column an Isinterned column etc..)
partial class MobilaisTelefons : Viedierice
{
public string modelis { get; set; }
public double svars { get; set; }
public SimKarte sim { get; set; } = new SimKarte();
public override string ToString()
{
return String.Join(";",
modelis, svars, sim.veids, operetajsistema, ekranaIzmers, irHDMI, krasa, razotajs.razotajaNosaukums,
sim.numurs, razotajs.valsts);
}
}
partial class MobilaisTelefons {
public string SimVeids { get => sim.veids; set => sim.veids = value; }
public string SimNumers { get => sim.numers; set => sim.numers = value; }
public string RazotajsRazotajaNosaukums { get => razotajs.razotajaNosaukums; set => razotajs.razotajaNosaukums = value; }
public Valsts RazotajsValsts { get => razotajs.valsts; set => razotajs.valsts = value; }
}
Few tips:
I made the extension of the class partial so you can put it in another file. Hiding its members from intellisense would be hard work
the Enum column will probably show as an int. if you want it to be sensible, use a DataGridViewComboBox column bound to a list of all the enum values/names. On the column, set the DataMember to "RazotajsValsts", the DataSource to the list of enums, the DisplayMember to the property representing the enum name and the ValueMember to the property representing the enum value. See Enum.GetValues.
Enums should only have a plural name (if valsts is plural) if they are flags
classes should not have a plural name
public properties names should be in PascalCase not camelCase
I simplified your tostring: you don't need to call to string on everything; string join will do it. You especially don't need to call tostring on a string

C#: How to statically get property attribute

I am looking for an elegant way of statically referencing a property attribute in C#. To give you an example, say I have this class:
public class A
{
[Attribute(Name="myAttributeName")]
public string Property1 { get; set; }
}
Now, I see the attribute as quite similar to a static member of a class, so in my mind, there should be an easy way to access the attribute from outside the class; e.g. through a similar operator to typeof or nameof (but it would return a list of attributes, since there may be multiple attributes to fetch). The way I would like to use this operator is as follows:
public class B
{
// Through an attribute definition
[Attribute2(attrof(A.Property1))]
public string Property2 { get; set; }
// In a method
public void method()
{
var attrs = attrof(A.property1);
}
}
I think I have found one way to make it work with two parameters like the example below - at least for the method invocation. Passing variables to attributes doesn't seem to work in C#, but that's nevertheless the way I'd like to construct my code.
public class C
{
public static object[] GetAttrs(Type type, string propertyName)
{
return type.GetProperty(propertyName).GetCustomAttributes(true);
}
}
public class A
{
[Attribute1(Name="myAttributeName")]
public string Property1 { get; set; }
}
public class B
{
// Through an attribute definition
// Unfortunately, passing variable to attrs not supported
// so this does not work
[Attribute2(C.GetAttrs(typeof(A), nameof(A.Property1)))]
public string Property2 { get; set; }
// In a method
public void method()
{
var attrs = C.GetAttrs(typeof(A), nameof(A.Property1));
}
}
However, it feels tedious to pass references to both the class and property, when syntactically, A.Property1 contains information about both - something a compiler should be able to draw information from. Therefore, I wonder if any such operator exists today, or if there are any other ideas on how this functionality could be achieved?
EDIT: I just thought about the B.Property2 attribute definition one more time and thought that it should still be possible to get this working, since I think attributes are constant. Or am I missing something here?
There is no default operator for such case, but you could implement something similar. Code to extract value from A.Property1 attribute is in Main function
using System;
using System.Reflection;
namespace ConsoleApp16
{
public class CustomAttribute : Attribute
{
public string Name { get; }
public CustomAttribute(string name)
{
Name = name;
}
}
public class ReferenceAttribute : Attribute
{
public string PropertyName { get; }
public Type Type { get; }
public ReferenceAttribute(Type type, string propertyName)
{
Type = type;
PropertyName = propertyName;
}
}
public class A
{
[Custom("text")]
public string Property1 { get; set; }
}
public class B
{
[Reference(typeof(A), nameof(A.Property1))]
public string Property { get; set; }
}
class Program
{
static void Main(string[] args)
{
var referenceAttribute = typeof(B).GetProperty(nameof(B.Property))
.GetCustomAttribute<ReferenceAttribute>();
var customAttribute = referenceAttribute.Type.GetProperty(referenceAttribute.PropertyName)
.GetCustomAttribute<CustomAttribute>();
Console.WriteLine(customAttribute.Name);
}
}
}

How to allow nested class instances modification in PropertyGrid not having access to inspected classes code?

Say we have a simple class model with classes as feilds (inside compiled, not modifiable Dll):
public class SubSubClassTest {
public int Data { get; set; }
}
public class SubClassTest {
public string InnerStr { get; set; }
public int InnerInteger { get; set; }
public SubSubClassTest InnerLoad { get; set; }
public SubClassTest() {
InnerLoad = new SubSubClassTest();
}
}
public class Test {
public string Str { get; set; }
public int Integer { get; set; }
public SubClassTest Load { get; set; }
public Test() {
Load = new SubClassTest();
}
}
And we want to edit it using PropertyGrid.
public partial class ApplicationForm : Form {
public ApplicationForm() {
InitializeComponent();
var test = new Test();
propertyGrid.SelectedObject = test;
}
}
And I do not have abilety to change classes (as I get them from Dll) and they have no [TypeConverter(typeof(ExpandableObjectConverter))] attribute on all members that are classes I get sush picture:
And members that are from my namespace class type are not editable.
If all such members havd [TypeConverter(typeof(ExpandableObjectConverter))] attribute I would have another picture and all would be fine:
I wonder how to make PropertyGrid use PropertyGrid for all nested classes?
You could try changing the TypeConverterAttribute value using PropertyDescriptor and Reflection. I wouldn't recommend to do this but to show that its possible I have added the sample code. I verified with your example and it works. But I cannot assure that it would work in all scenarios. Food for thought...
var test = new Test();
SetTypeConverterAttribute(test);
propertyGrid.SelectedObject = test;
private void SetTypeConverterAttribute(Test test)
{
foreach (PropertyDescriptor item in TypeDescriptor.GetProperties(test))
{
TypeConverterAttribute attribute = item.Attributes[typeof(TypeConverterAttribute)] as TypeConverterAttribute;
if (attribute != null && item.PropertyType == typeof(SubClassTest))
{
FieldInfo field = attribute.GetType().GetField("typeName", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
{
field.SetValue(attribute, typeof(ExpandableObjectConverter).FullName);
}
}
}
}
If you have control over the classes, you can create a common base class and decorate this base class with the TypeConverterAttribute. In that case, any property that will reference any instance of this type will use the ExpandableObjectConverter, unless this behavior is overridden by the property (using another TypeConverterAttribute).

Using Reflection and GetValue problems

I have an abstract class that looks like so:
public abstract class PageObjectsBase
{
public abstract string FriendlyName { get; }
public abstract string PageObjectKeyPrefix { get; }
public abstract string CollectionProperty { get; }
}
And a class that derives from PageObjectsBase:
public class PageRatingList : PageObjectsBase
{
public IList<PageRating> PageRatings { get; set; }
public PageRatingList()
{
this.PageRatings = new List<PageRating>();
}
public override string CollectionProperty
{
get
{
var collectionProperty = typeof(PageRatingList).GetProperties().FirstOrDefault(p => p.Name == "PageRatings");
return (collectionProperty != null) ? collectionProperty.Name : string.Empty;
}
}
public override string FriendlyName
{
get
{
return "Page feedback/rating";
}
}
public override string PageObjectKeyPrefix
{
get
{
return "pagerating-";
}
}
}
And a PageRating class which PageRatingList.PageRatings is holding a collection of:
public class PageRating : PageObjectBase
{
public int Score { get; set; }
public string Comment { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
The PageRatingList is being stored in a database (EPiServer's Dynamic Data Store, more specifically using the Page Object Manager). I need to create some reporting functionality and am essentially loading all reports that derive from PageObjectBase. When it comes to returning the data, the code will never know at compile time what type of data it is to load, so I am using Reflection. In my reporting class I have:
//this gives me the right type
var type = Type.GetType("MyNameSpace.PageRatingList", true);
var startPageData = this._contentRepository.Get<PageData>(startPage);
PageObjectManager pageObjectManager = new PageObjectManager(startPageData);
//this loads the instances from the DB
var props = pageObjectManager.LoadAllMetaObjects()
.FirstOrDefault(o => o.StoreName == "Sigma.CitizensAdvice.Web.Business.CustomEntity.PageRatingList");
//this gives me 4 PropertyInfo objects (IList: PageRatings, string : CollectionProperty, string :FriendlyName, string : PageObjectKeyPrefix)
var properties = props.Value.GetType().GetProperties();
I can then iterate through the PropertyInfo objects using:
foreach (var property in properties)
{
//extract property value here
}
The issue I am having is that I cannot figure out how to get the value of each of the propertyinfo objects. In addition, one of those properties is type List and again we wont know the type of T until runtime. So I also need some logic that checks if one of the PropertyInfo objects is of type List and then provides access to each of the properties in the List - the List being of type PageRating.
Can anyone help here? I've not really used reflection in the past so I am winging my way through it, rightly or wrongly!
Many thanks
Al
I may be missunderstanding the problem, but i think you may use something like this:
var props = new PageRatingList(); /*actual instanse of the object, in your case, i think "props.Value" */
var properties = typeof(PageRatingList).GetProperties();
foreach (var property in properties)
{
if (property.PropertyType == typeof(IList<PageRating>))
{
IList<PageRating> list = (IList<PageRating>)property.GetValue(props);
/* do */
}
else
{
object val = property.GetValue(props);
}
}
Hope this helps to find your solution.

How to inherit from class that could be inherited by other classes and keep other classes properties (for view model)

I'm stuck in my inheritances bloating here:
First let me explain the premise of my problem.
My Model:
public class Person
{
[Key]
public int PersonId { get; set; }
[MaxLength(100)]
public string Name { get; set; }
}
public class SuperHero:Person
{
[MaxLength(100)]
public string SuperHeroName { get; set; }
public virtual ICollection<SuperPower> SuperPowers{ get; set; }
}
Now, I am trying to create my viewModels for my MVC website, I have those base classes that need to be inherited by all other viewmodel displaying/editing a Person/SuperHero:
public class BasePersonViewModel
{
public string Name { get; set; }
ctors()
}
public class BaseSuperHeroViewModel : BasePersonViewModel
{
public List<string> SuperPowers{ get; set; }
ctors()
}
Here is where I am stuck, I am trying to define only one ViewModel that could be used regarless of the base class and access property of Person and/or SuperHero (if the Person is a superhero). I've been pulling my hair out but so far only found a solution which i don't like:
Example:
public class SomeViewModel<T> where T : BasePersonViewModel
{
public BasePersonViewModel obj;
public DateTime BirthDate { get; set; }
public SomeViewModel(Person data) //: base(data)
{
if (data is SuperHero)
obj = new BaseSuperHeroViewModel (data);
else
obj = new BasePersonViewModel(data);
}
}
While this would work it's really not sexy to use. And on top of that, I could have another ViewModel that inherit from SomeViewModel as well.
Is there a cleaner way to achieve this?
Edit
My main goal is to be able to able to cast my SomeViewModel depending on the one of the baseclass. Let's say do something like in my Controller:
if myclass is SomeViewModel (of type SuperHero)
Exactly how you do it for Person/SuperHero db retrival/check
var data = context.Person.first(w=> w.Id==1)
if (data is SuperHero)
..
I would like this because I would like to use the same viewmodel let's say to list superhero and person, and just display slightly differently if it's a superhero
Edit 2
I was trying to avoid using the whole Model.Obj to be able to see it directly with the Model... But the more i think about it, the more I think this is not possible really... On top of that I would like to extend some other superHero specific properties in SomeViewModel (only if SomeViewModel is a superhero), that are not declared in the BaseSuperHeroModel one... Let's say in SomeViewModel I want the field 'ComesFromPlanet' only if superhero.
Edit 3
I thought about another way to do it, but it obviously creating various ViewModel.
For the most general case (all fields that are shared for all Person) I would keep my base:
public class BasePersonViewModel
{
public string Name { get; set; }
ctors()
}
I interface specific Person:
public Interface IBaseSuperHero
{
[MaxLength(100)]
public string SuperHeroName { get; set; }
public List<string> SuperPowers{ get; set; }
}
I would keep as well OtherViewModel like this:
public class SomeViewModel:BasePersonViewModel
{
Public datetime Birthdate {get;set;}
}
Then I would create a specific SomeviewModel for other Person inheritant and used interfaces to have old and new properties.
For example:
public class SomeViewModelSuperHero:SomeViewModel, IBaseSuperHero
{
public string OriginalPlanet {get;set;}
}
Is this a clean solution?
Sorry I'm sure I am not clear about this, but I try !
Thanks for your input and time.
I am trying to define only one ViewModel that could be used regarless of the base class and access property of Person and/or SuperHero (if the Person is a superhero)
Assuming you'd return default values for super-hero properties when the model is not a super-hero, you could do something like this:
public class PersonOrSuperHeroViewModel {
private Person person;
private SuperHero superHero;
public PersonOrSuperHeroViewModel(Person personOrSuperHero) {
if (personOrSuperHero is SuperHero) superHero = personOrSuperHero;
person = personOrSuperHero;
}
public IsSuperHero { get { return superHero != null; } }
... // super-hero properties only work when IsSuperHero == true
}
How about something like
public class Person {
public virtual BasePersonViewModel MainViewModel {
get { return new BasePersonViewModel(this);}
}
}
public class SuperHero : Person {
public override BasePersonViewModel MainViewModel {
get { return new BaseSuperHeroViewModel(this);}
}
}
So if all your people classes override the MainViewModel property to return the appropriate view, you don't need
public BasePersonViewModel obj;
public SomeViewModel(Person data) {
if (data is SuperHero)
obj = new BaseSuperHeroViewModel (data);
else
obj = new BasePersonViewModel(data);
}
Because you can have
public BasePersonViewModel obj;
public SomeViewModel(Person data) { obj = data.MainViewModel; }
which will work however many subclasses of person you have.

Categories