I have below the object and its internal objects and I have added custom attributes to the property names which are required for the report.
public class Space
{
public SpaceIdentity SpaceIdentity { get; set; } = new();
public SpaceGeometry SpaceGeometry { get; set; } = new();
public AirBalance AirBalance { get; set; } = new();
public EngineeringChecks EngineeringChecks { get; set; } = new();
}
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number", IsIncludedInReport2 = true)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name", IsIncludedInReport2 = true, IsIncludedInReport1 = true)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number", IsIncludedInReport1 = true)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name", IsIncludedInReport1 = true)]
public string RoomName { get; set; }
}
public class SpaceGeometry
{
public Vertex LocationPoint { get; set; } // this is not required
[DisplayNameWithUnits(DisplayName = "Space Area", Units = "(ft²)", IsIncludedInReport1 = true)]
public double FloorArea { get; set; }
}
.....
Here I am building an excel report, which I want to use property display name's as header column names of that report. Here are some of the properties attribute information used in multiple reports. What I did was I added a bool condition attribute like (isIncludedInReport1
) and loop through the properties of space
and loop through the properties of inner object(SpaceGeometry
) to get a particular property name and its attribute values based on this boolean condition.
What I am looking for here is without adding these bool attributes, is there any way to access the property names based on condition. I thought about adding interfaces, but that is not possible here because I have multiple inner classes having properties that I need to include in a single report.
Could anyone please let me know is there any other way to achieve this?
Update:
var columnResult = new OrderedDictionary();
GetReportHeaderColumnName(typeof(Space), columnResult);
public static void GetReportHeaderColumnName(Type type, OrderedDictionary headerNameByUnit)
{
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
if (propertyInfo.PropertyType.IsClass && !propertyInfo.PropertyType.FullName.StartsWith("System."))
{
if (propertyInfo.PropertyType == typeof(Overridable<double>))
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
else
{
GetReportHeaderColumnName(propertyInfo.PropertyType, headerNameByUnit);
}
}
else
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
}
}
protected static void AddReportHeaderName(OrderedDictionary columnResult, PropertyInfo propertyInfo)
{
if (propertyInfo.GetCustomAttributes(typeof(DisplayNameWithUnitsAttribute), true).Any())
{
var displayNameWithUnitsAttribute = (DisplayNameWithUnitsAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(DisplayNameWithUnitsAttribute));
if (displayNameWithUnitsAttribute.IsIncludedInReport2)
{
columnResult.Add(displayNameWithUnitsAttribute.DisplayName, displayNameWithUnitsAttribute.Units);
}
}
}
CodePudding user response:
What do you want to achieve by making it another way? I assume you want to make your code scalable so adding more reports is easier.
Do you still want to use attributes on the classes themselves? If so, you could make a new attribute, and tag your properties like so:
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number")]
[IncludeInReport(2)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name")]
[IncludeInReport(1)]
[IncludeInReport(2)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number")]
[IncludeInReport(1)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name")]
[IncludeInReport(1)]
public string RoomName { get; set; }
}
Or do you want to separate the report from the classes, so your report is defined in another file? If so, maybe a 2-dimensional list of property names would do the trick:
List<List<string>> Report1 = new()
{
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.Name)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomNumber)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomName)},
new(){nameof(Space.SpaceGeometry), nameof(SpaceGeometry.FloorArea)}
};
CodePudding user response:
Another way that doesn't use reflection is to just use a list of ReportProperty
public record ReportProperty<T>(
Func<T, string> ValueFunc,
string DisplayName,
string? Unit = null
);
List<ReportProperty<Space>> report1 = new(){
new( s => s.SpaceIdentity.Number, "Space Number"),
new( s => s.SpaceIdentity.RoomNumber, "Room Number"),
new( s => s.SpaceIdentity.RoomName, "Room Name"),
new( s => s.SpaceGeometry.FloorArea.ToString(), "Floor Area", "Ft2"),
};