Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC Iterate over Model Properties with Data Annotations

Problem Description

My problem is similar to this question but instead of applying Data Annotations to the property Name via reflection (handled by ModelMetadata.DisplayName) I am applying them to the value (not handled by ModelMetadata).

Detailed Description

In the context of an ASP.NET MVC program.
Suppose I have a Model class

public class Model
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string NickName { get; set; }
    public string Address { get; set; }
    public int Phone { get; set; }

    [Display(Name = "Start Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
    public DateTime StartDate { get; set }

    [Display(Name = "End Date")]
    [DataType(DataType.Date)]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
    public DateTime EndDate { get; set }
}

Then suppose this Model is used in at least 5 different views where the value of every property must be displayed in full. (Sometimes for every instance, other times for a handful, other times for one).

I can manually list each Property access within it's own
<td>@Html.DisplayFor(item.<Property>)</td>
for every view.

But that won't help me if later on the definition for Model expands to include new Properties (like Description and Relation and Reliability). Then I'd need to manually update every occurrence of the complete Model listing.

I can use reflection to iterate over a list of PropertyInfo's and save having to manually list each property by using
<td>@property.GetValue(item)</td>

But DisplayFor(x) does not support an expression as complex as x=>property.GetValue(item), and this means I lose the Data Annotations that format my DateTime as
01/01/1990
instead of
01-Jan-90 12:00:00 AM
and would likely also result in the loss of all types of annotation including validation.

Problem Solutions

So far I have considered (and in some cases attempted) the following solutions:

  • [Failed] Manually craft an Expression which emulates the functionality of @property.GetValue(item)
    [Edit]
  • [Failed] Pass DisplayFor a MethodInfo object representing the Property Accessor DisplayFor(x => property.GetGetMethod()) as well as .Invokeing it on x.
    [/Edit]
  • Retrieve the value manually as normal, and
    • execute a method on it to manually retrieve and implement Annotation Data on it prior to insertion in the view element as this question suggests, or
    • Re-implement the DisplayFor handling of Data Annotations on an as-needed basis in a Display Template View and apply that directly to the value via DisplayFor as this question suggested
  • Refactor the Model class to contain only a list(SortedList?) of 'Prop' instances, where 'Prop' is a class representing a Property with a Name and Value element.

This last solution would turn the broken
@Html.DisplayFor(m=>property.GetValue(item)
into a theoretically working
@Html.DisplayFor(m=>item.Properties[i].Value)
Which aside from the slightly unintuitive need for getting a property called Name (Properties["Name"]) by (.Value), seems the most workable solution, at the cost of Model clarity.

[Edit]
Most recently I have created a Utility method which retrieves the DisplayFormatAttribute from the PropertyInfo and returns either the DisplayFormatString or the default of "{0}" if a format string was not annotated. I have then used it to create a collection of preformatted property values within a ViewModel. This seems for now, to be the most elegant way I know of to decouple the View from the Model as much as possible while still retrieving the necessary data from it.
[/Edit]

The Question

This is at the moment, purely a learning exercise, but I would like to know...
Is it possible to succeed where I have failed and both have my Reflection cake and eat the Data Annotations too? Or must I seek an alternative solution?
If I must seek an alternative solution, are there routes I have missed, or am I at least on the right track?

like image 380
Robere Starkk Avatar asked Nov 07 '22 22:11

Robere Starkk


1 Answers

Maybe something similar to:

@foreach (var property in Model.GetType().GetProperties())
{
    <li>@property.GetValue(Model, null)</li>
}
like image 150
Cristian Szpisjak Avatar answered Nov 11 '22 15:11

Cristian Szpisjak