I am working on a project where I need to output a few hundred properties on to the screen. To save myself lots of tedious markup, I decided to use reflection.
//markup removed to keep this concise
@for (var i = 0; i < Model.SiteAndJobDetails.GetType().GetProperties().Count(); i++)
{
@Model.SiteAndJobDetails.GetType().GetProperties()[i].Name
@Model.SiteAndJobDetails.GetType().GetProperties()[i].GetValue(Model.SiteAndJobDetails, null)
}
Although slower to render, this will save me writing out about 2 hundred properties and values with HTML helpers. At least, that was the plan. However, I need to use @Html.DisplayNameFor
or something similar to pick up the Display
attribute value from the property.
My intial thoughts were
@Html.DisplayNameFor(m=>@Model.SiteAndJobDetails.GetType().GetProperties()[i].Name)
But that does not work, I would imagine because I am using reflection here to get the property name. Is there another way?
You can get it using the Metadata (that's what the framework does anyway):
string displayName = ViewData.ModelMetadata.Properties
.Where(x => x.PropertyName == Model.SiteAndJobDetails.GetType()
.GetProperties()[i].Name)
.SingleOrDefault()
.DisplayName;
@Andrei was correct to use the ViewData.ModelMetadata
but had the syntax slightly off. The correct syntax is
@ViewData.ModelMetadata.Properties.First(x => x.PropertyName == "SiteAndJobDetails")
.Properties.SingleOrDefault(x => x.PropertyName == Model.SiteAndJobDetails.GetType().GetProperties()[i].Name)
.DisplayName
The final solution is to check the property exists, if it does use it, otherwise use the property name
@if (!string.IsNullOrEmpty(@ViewData.ModelMetadata.Properties.First(x => x.PropertyName == "SiteAndJobDetails").Properties.SingleOrDefault(x => x.PropertyName == Model.SiteAndJobDetails.GetType().GetProperties()[i].Name).DisplayName))
{
@ViewData.ModelMetadata.Properties.First(x => x.PropertyName == "SiteAndJobDetails").Properties.SingleOrDefault(x => x.PropertyName == Model.SiteAndJobDetails.GetType().GetProperties()[i].Name).DisplayName
}
else
{
@Model.SiteAndJobDetails.GetType().GetProperties()[i].Name
}
Alternatively, add this to your razor:
@functions {
string R<TCLASS>(Expression<Func<TCLASS, Object>> expression) //Lambda = x => x.TPROPERTY
{
var memberExpression = expression.Body as MemberExpression;
if(memberExpression == null)
{
memberExpression = (MemberExpression) ((UnaryExpression)expression.Body).Operand;
}
return memberExpression.Member.Name;
}
}
Then you can:
@(R<ModelClassName>(x => x.PropertyName)) //Outputs "PropertyName"
I like using this when writing JavaScript that receives JSON. It lets you do this:
<script>
function recieveJsonResult(classNameDto) {
var classProperty = classNameDto.@(R<ClassNameDto>(x => x.PropertyName));
}
</script>
This way you get autocomplete inside the lambda and you are free to rename the property without worrying about breaking your front end code.
I prefer this answer: https://stackoverflow.com/a/31448493/9266796
Updated to today's syntax:
var displayName = property.GetCustomAttribute<DisplayAttribute>().Name;
Or this one's even easier actually: https://stackoverflow.com/a/19940277/9266796
@Html.Label(property.Name);