Problem description
I'm currently working on a Wizard mechanism in our ASP.NET MVC application. However, I've faced a problem while trying to bind model in the view. In short:
I've got a wizard model which looks more or less like this:
class WizardViewModel {
public IList<StepViewModel> Steps { get; set; }
// ...
}
Each step except for last has got its own model. The last step (summary) takes whole WizardStepModel and is used only to display data (via disabled controls). Displaying values from all steps leads to this kind of code in the view:
#Html.DropDownListFor(
m => ((ConcreteStepModel)Model.Steps[0]).SelectedValue,
((ConcreteStepModel)Model.Steps[0]).SelectList,
new { disabled = "disabled" }
)
The code works, but continuous casting base step model to a concrete class only to get the value:
Is uncomfortable,
makes code less readable.
What I tried to do?
I thought that I could create an alias for each step:
#{
ConcreteStepModel stepOne = (ConcreteStepModel)Model.Steps[0];
}
And then:
#Html.DropDownListFor(
m => stepOne.SelectedValue, stepOne.SelectList, new { disabled = "disabled" }
)
It works for most of controls, but not for DropDownList. For some reason, value of the dropdown is bound incorrectly and shows first option instead of the selected one.
Question
Is there another way which I could use for creating some kind of aliases for steps from the wizard so that I don't have to perform casting each time I need to get a value? Or maybe I am doing something wrong? I'd be grateful for any help.
Since your 'last step' is just a summary and presumably used as a final 'confirmation step' before saving to the database, then you should not be generating disabled form controls for your properties. It would be rendering anywhere 2-5 times the html that is necessary which will just degrade performance.
Instead just generate the value of the property as text, for example
<div class="field">
<div class="field-label">#Html.DisplayNameFor(m => m.Steps[0].SomeProperty)</div>
<div class="field-value">#Html.DisplayFor(m => m.Steps[0].SomeProperty)</div>
</div>
and use the class names to style the layout (and the result would no doubt save a lot of screen space as well).
For the dropdownlist properties, you would need to add an additional property associated with the selected text, so in addition to public int SelectedValue { get; set; } used in the edit view, you would include an associated public string SelectedText { get; set; } property for the 'final step' view.
It also appears from your code that StepViewModel is an abstract base class and you have a concrete class for each 'step'. If that is the case, then it would be better for your WizardViewModel to contain properties for each step
public class WizardViewModel
{
public Step1Model Step1 { get; set; }
public Step2Model Step2 { get; set; }
....
}
which means if you really did want to generate form controls, it would not be be necessary to cast the type. The code would just be
#Html.DropDownListFor(m => m.Step1.SelectedValue, Model.Step1.SelectList, new { disabled = "disabled" })
Related
I am learning asp.net mvc , using visual studio community 2017, and as a sort of teaching project I am making a web app that keeps track of exercise work outs. My model consists of WorkOut objects that have a list (or ICollection more specifically) of Exercise, and each Exercise has an ICollection. Heres the basics of my model classes.
public class WorkOut
{
public int Id { get; set; }
public int Length { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Exercise> ExerciseList { get; set; }
}
public class Exercise
{
public int Id { get; set; }
public string Name { get; set; }
public int WorkOutId { get; set; }
public virtual WorkOut WorkOut { get; set; }
public virtual ICollection<RepUnit> Sets { get; set; }
}
public class RepUnit
{
public int Id { get; set; }
public int Rep { get; set; }
public int? Weight { get; set; }
public int ExerciseId { get; set; }
public virtual Exercise Exercise { get; set; }
}
Generating a view automatically with WorkOut as a model leads to Create action and view that only generates a Length and Date property. In general, auto generated view and controllers only add the non virtual properties. So I figure maybe I have to do a multistep creation process; Create a workout, create an exercise and add reps to it, add that exercise to the work out, either stop or add another exercise. So I figured Id let VS to some of the work for me, and I make controllers and views for each of the model object typers (WorkOutsController, ExercisesController, RepUnitsController), and later I would trim out the uneeded views or even refactor the actions i actually use into a new controller.
So WorkOutsController my POST action is this.
public ActionResult Create([Bind(Include = "Id,Length,Date")] WorkOut workOut)
{
if (ModelState.IsValid)
{
db.WorkOuts.Add(workOut);
db.SaveChanges();
return RedirectToAction("Create","Exercises",new { workoutId = workOut.Id });
}
return View(workOut);
}
So I carry the workoutId to the Exercise controller but this is where I am unsure how to proceed. I want to keep carrying around the workoutId and for the next step, where I give the exercise a name, also show the associated date that was just added. The only thing I could think to do was instantiate an Exercise in the GET action of ExerciseController like so.
public ActionResult Create(int workoutID)
{
Exercise ex= new Exercise();
ex.WorkOutId=workoutID;
ex.WorkOut=db.WorkOuts(workoutID);
return View(ex);
}
This seems terrible and I've not seen anything like this done in any examples, but it seems to work. The same exercise object is brought back to my POST create action here
public ActionResult Create([Bind(Include = "Id,Name,WorkOutId")] Exercise exercise)
{
if (ModelState.IsValid)
{
db.Exercises.Add(exercise);
db.SaveChanges();
return RedirectToAction("Create", "RepUnits", new { exerciseId = exercise.Id });
}
return View(exercise);
}
which as you see calls the RepUnits controller and associated Create action. There I do something very similar; create a rep object and pass it to the view, and essentially I add reps until I'm done. Eventually I will create navigation to either go back to add a new exercise or go back to an Index view.
So to sum up, it seems wasteful to be passing entire objects around, and maybe my whole implementation is wrong and I should be trying to somehow do this all on one form. Up to this point googling hasnt found me much because I wasnt sure what questions to be asking, however this post Creation of objects using form data within an ASP.NET MVC application just popped up in the similar question dialogue and the app in question is coincidentally very similar! However when the OP mentions passing the workoutId around, how is this accomplised? I thought to maybe use the ViewBag but how do I get the view to handle this Id?
I had though to try, as an example
public ActionResult Create(int workoutId)
{
ViewBag.WoID = workoutId;
return View();
}
in the ExercisesController and then in the associated Create view:
#Html.Hidden("WorkOutId", new { ViewBag.WoID })
But later in the view when I try to reference the workout date it comes up blank
<div class="form-group">
#Html.LabelFor(model => model.WorkOut.Date, "Work Out On:", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model=>model.WorkOut.Date)
</div>
</div>
Should I be doing something like this in the view:
#Model.WorkOutId=ViewBag.WoID;
which doesnt work for some reason (Compiler Error Message: CS1525: Invalid expression term '='), but is that along the lines of how I pass these ids around?
The scaffolded views are intentionally simplistic. Dealing with related items requires multiple considerations, and Visual Studio won't make those for you. However, you can and are very encouraged to alter the scaffolded views to your particular needs.
To create exercises in the same view as your workout for example, you need only generate fields for Exercise with names that will allow the modelbinder to bind the posted data. For collection properties that means something like CollectionProperty[N].Property, where N is an index.
For example, you can initialize your workout with three exercises:
var model = new Workout
{
ExerciseList = new List<Exercise>
{
new Exercise(),
new Exercise(),
new Exercise()
}
};
Then, in your view:
#for (var i = 0; i < Model.Exercises.Count(); i++)
{
#Html.EditorFor(m => m.ExerciseList[i].Name)
}
However, there's one thing to note here: ICollection is not indexable. As a result, to do it this way, you'd need a property typed as List<Exercise>. This is where view models come in very handy. Nevertheless, there is a way around this: you can use EditorFor on the Excercises collection instead. For example, the above code would be reduced to just:
#Html.EditorFor(m => m.ExerciseList)
EditorFor is a "templated helper", which means simply that it uses templates to render what's passed to it. Thankfully, it has some defaults, so to a point, you don't need to worry about that, but it become problematic. For example, here, Razor will simply iterate over the items in ExerciseList and render the template for Exercise for each. Since Exercise is a custom type and doesn't have a default template, it will then introspect the class and render a template for each public property on Exercise. Sometimes this works just fine. For example, Name will be rendered with a text box, as it should be. However, you'll also get text boxes for Id and WorkoutId, which you wouldn't want to even be visible on your form.
You can solve this issue by creating your own editor template for Exercise, by adding a view to Views\Shared\EditorTemplates\Exercise.cshtml. This view would have a model of Exercise, then, and would simply include a text box for your name property. Then, when you use EditorFor on ExerciseList as above, it will render each Exercise utilizing that view.
With all that out of the way, though, you've likely realized that this is still somewhat limiting: you have to initialize with a certain number of exercises and then that's all you get. That's where JavaScript comes in. Instead of iterating over a predefined list of exercises, you can simply dynamically add a new block of exercise fields as needed (or remove existing blocks). However, writing JavaScript for this task manually would be very painstaking and dense. At this point, you're better off utilizing something like Knockout.js, Angular, or similar. These libraries, among other things, give you two-way databinding, so you could simply set up a JavaScript template for what a block of exercise fields will look like, and then bind that to an ExceriseList member of a JavaScript object (your client-side "view model"). You could then cause these fields to repeat simply by adding or removing items from this JS array. Obviously, there's much more that goes into this, but that's the basic framework. You'd need to consult the individual documentation of the library you went with to determine exactly how to set everything up.
You can then rinse and repeat all this for other levels of relationships as well. In other words, it's entirely possible to post this entire object graph of a workout with multiple exercises, each with multiple rep units, etc. all in one go with one view.
See below for updated summary...
I understand that using the 'For' Html Helpers is preferred but I'm having a problem with DropDownListFor when using it as a multi-select.
This example (DropDownList) works perfectly:
#Html.DropDownList(
"ProtocolDisciplines",
new MultiSelectList(Model.Disciplines, "DisciplineId", "Discipline", Model.ProtocolDisciplines.Select(pd => pd.DisciplineId)),
new { #class = "form-control", multiple = "multiple", size = "8" }
)
This example (DropDownListFor) works perfectly EXCEPT the default value(s) does not get set:
#Html.DropDownListFor(
model => model.ProtocolDisciplines,
new MultiSelectList(Model.Disciplines, "DisciplineId", "Discipline", Model.ProtocolDisciplines.Select(pd => pd.DisciplineId)),
new { #class = "form-control", multiple = "multiple", size = "8" }
)
UPDATES
Based on what I'm learning I've updated from the original post. Here is the code that is still not working. To be clear, it is doing everything perfectly EXCEPT it is not selecting the default value when rendered. In the example I'm working with there is only a single default value.
#Html.ListBoxFor(
model => model.ProtocolDisciplines,
new MultiSelectList(Model.Disciplines, "DisciplineId", "Discipline", Model.ProtocolDisciplines),
new { #class = "form-control", size = "8" }
)
I've have made certain that Disciplines (the list of all 16 Disciplines in the db) and ProtocolDisciplines (the list of Disciplines that belong to the Protocol) are the same type (DisciplineViewModel). Further, that class (see below) contains only 2 properties (DisciplineId and Discipline).
I have a breakpoint where the model is returns to the view and I have verified that both Disciplines and ProtocolDisciplines have the values expected so I am currently focusing on the view and the ListBoxFor helper. As a note, I have also tried the exact same code with a DropDownListFor helper with identical behavior).
I suspect the problem is in the creation of the MultiSelectList. As you can see, I'm using the overload (IEnumerable ListItems, string DataValue, string DataText, IEnumerable SelectedValues). It would seem that the SelectedValues are simply not getting a match on anything in the ListValues but I can't figure out why. The types used in the two are the same, the DataValue and DataTypes names match the members of the types (just to be safe). I know the ListItems is correct because the list renders them correctly.
I'm at a loss.
Reference:
public partial class DisciplineViewModel
{
public Guid DisciplineId { get; set; }
public string Discipline { get; set; }
}
Here is the model:
public partial class ProtocolViewModelEdit
{
[Key]
public Guid ProtocolId { get; set; }
[Display(Name = "Name")]
public string Protocol { get; set; }
public string ProtocolType { get; set; }
[Display(Name = "Type")]
public Guid ProtocolTypeId { get; set; }
[Display(Name = "Status")]
public Guid ProtocolStatusId { get; set; }
public virtual ICollection<ProtocolTypeViewModel> ProtocolTypes { get; set; }
public virtual ICollection<ProtocolStatusViewModel> ProtocolStatuses { get; set; }
public virtual ICollection<DisciplineViewModel> ProtocolDisciplines { get; set; }
public virtual ICollection<ProtocolXProgramViewModel> ProtocolPrograms { get; set; }
public virtual ICollection<DisciplineViewModel> Disciplines { get; set; }
public virtual ICollection<ProgramViewModel> Programs { get; set; }
}
You referred to a post on the MSDN forums wherein the OP describes the following:
1) The selectedValues parameter must be populated with a collection of key values only. It cannot be a collection of the selected objects, as the HtmlHelper does not apply the dataValueField to this collection for you.
2) If using the ListBox you cannot set the name parameter the same as a Model property. Also you cannot name the ViewBag property that will contain the collection of items the same as the Model property.
3) If using the ListBoxFor it gets even more wonky. You should name the ViewBag property that will contain the collection of items the same as the Model property name. Now when you use the ListBoxFor within the View you must use a ViewBag property that does not exist (this is important!). The HtmlHelper.ListBoxFor will look automatically for a ViewBag property with the assigned Model property name.
None of these are actual issues. A SelectList ultimately has to be translated to/from an HTML select element, which can only work with simple types (string, int, etc.). Actually, everything is a string, and it's only the work of the model binder that translates the posted values into more specific types like int. As a result, it's obvious why you cannot bind a list of objects.
The other two mentioned issues are a result of ModelState. The values of bound form fields are determined by what's in ModelState, which is composed of values from Request, ViewData/ViewBag, and finally Model, as a last resort. If you set a SelectList in ViewBag with the same name as a property on Model, then the value for that key in ModelState will be that SelectList rather than the actual selected values, and your select will therefore have no selected items, because none of the option values will of course match that SelectList instance. Again, this is just standard behavior, and it's only a "bug" if you're not aware of how things work, and don't realize the implications of what you're doing.
Your issue here is exactly the first problem. You're passing a list of objects as the selected values, and there's simply no way to bind that properly to an HTML select element. However, things are far easier if you don't even bother to create your own MultiSelectList anyways. All the helper needs is IEnumerable<SelectListItem>. Razor will take care of creating a SelectList/MultiSelectList and setting the appropriate selected values. Just do:
#Html.ListBoxFor(
m => m.ProtocolDisciplines,
Model.Disciplines.Select(d => new SelectListItem { Value = d.DisciplineId.ToString(), Text = d.Discipline }),
new { #class = "form-control", size = 8 }
)
UPDATE
To answer you question about how Razor "knows", like I said in my answer, the info comes from ModelState. However, as pointed out by Stephen in the comments below, the property you're binding this to is a collection of objects. That's never going to work. Again, the posted values from an HTML select element will always be simple types, not objects. As a result, you need a property that the model binder can bind the posted data to, and then you need to use that information to lookup the actual objects you need, before finally setting something like your ProtocolDisciplines property. In other words:
public List<int> SelectedProtocolDisciplines { get; set; }
public IEnumerable<SelectListItem> DisciplineOptions { get; set; }
Since you're using a view model, it's better if you include the select list items on that view model, so I added a property for that. In your actions (GET and POST), you'll need to set this property:
model.DisciplineOptions = model.Disciplines..Select(d => new SelectListItem {
Value = d.DisciplineId.ToString(),
Text = d.Discipline
});
Since you'll need to call that in both the GET and POST actions, you might want to factor it out into a private method on your controller that both can call. Then, in your view:
#Html.ListBoxFor(m => m.SelectedProtocolDisciplines, Model.DisciplineOptions, new { #class = "form-control" })
Finally, in your POST action:
var protocolDisciplines = db.Disciplines.Where(m => model.SelectedProtocolDisciplines.Contains(m.DisciplineId));
Then, if this is a "create" method, you can simply set the appropriate property on your entity with that. If you're editing an existing entity, you'll need to do a little bit more work:
// Remove deselected disciplines
entity.ProtocolDisciplines
.Where(m => !model.SelectedProtocolDisciplines.Contains(m.DisciplineId))
.ToList()
.ForEach(m => entity.ProtocolDisciplines.Remove(m));
// Add new selected disciplines
var addedDisciplineIds = model.SelectedProtocolDisciplines.Except(entity.ProtocolDisciplines.Select(m => m.DisciplineId));
db.Disciplines
.Where(m => addedDisciplineIds.Contains(m.DisciplineId))
.ToList()
.ForEach(m => entity.ProtocolDisciplines.Add(m));
This extra footwork is necessary to maintain the existing, unchanged M2M relationships.
I am fairly new to MVC, but have quite a bit of experience in development in general, and am having an issue with MVC request life cycle it seems.
Will try to keep this simple, even tho the project is a bit complex in some areas.
I have a view bound to a view model that has a few complex list properties. These properties are displayed via checkboxes who's IDs are not directly related to any property in the model, but instead related to the IDs of the objects in the List<>. Because of this, the checked values do not automatically get applied to the model on POST.
To get around that, I added code in the Action method in the controller that parses the proper controls (in the Request.Form collection) and assigns the checked/selected value to the proper list items in the model.
This works perfectly up to a point.
Now, I also use Fluent Validation, and the problem is when performing custom validation rules when posting a new model to the server. The Validation routine is firing BEFORE the controller's action method, and thus before my processing of the list objects.
So, my question is, is there a way I can override the initial call to the model validation so I can just call the validation manually after my processing? I know I can do that which will fix the problem without overriding the initial call, but some of the validation takes a bit of time to process since it requires linq queries to a live database, so I do not want the validation to fire 2 times - that will quite literally double the time it takes to return no matter if the model is valid or not.
EDIT: Adding a example:
namespace Models
{
[Validator(typeof(MemberValidator))]
public class ViewMember
{
public int MemberID { get; set; }
public short RegionID { get; set; }
public List<PropTypeInfo> PropTypes { get; set; }
}
}
PropTypeInfo class:
public class PropTypeInfo
{
public byte ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Selected { get; set; }
public PropTypeInfo(byte i, string n, string d, bool sel)
{
ID = i;
Name = n;
Description = d;
Selected = sel;
}
public static List<PropTypeInfo> GetAll(bool selected = false)
{
List<PropTypeInfo> output = new List<PropTypeInfo>();
OpenAccess.Context context = new OpenAccess.Context();
var list = (from f in context.Prop_Types orderby f.PropType select f).ToList();
foreach (OpenAccess.WebrentzServerPayments.Models.Prop_Type p in list)
output.Add(new PropTypeInfo(p.PropType, p.PropName, p.DisplayText, selected));
return output;
}
}
now here is the code in the view that renders the checkboxes for each item in the list:
<div class="Column Emp-PropTypes">
#foreach (WebrentzServerPayments.Models.PropTypeInfo ptype in Model.PropTypes)
{
<div style="float:right;width:20%;font-weight:bold;">
#Html.CheckBox("ptype_" + ptype.ID, ptype.Selected, new {Value=ptype.ID}) #Html.Raw(" ") #ptype.Name
</div>
}
</div>
And here is the code I use in the Controller Action method to pull that data back in to the List:
foreach (PropTypeInfo info in member.PropTypes)
info.Selected = form[string.Format("ptype_{0}", info.ID)].Contains(info.ID.ToString());
As a little background, a "PropType" is a type of property (house, condo, apartment) - there are about 2 dozen of them, and more can be added/removed at any time. The list in the class called "PropTypes" is first populated with the Name, Description and ID from a table in the database that lists all the available proptypes for that region.
We then will mark the proptypes as "selected" if the user has chosen that particular type. Those are saved to a table called Member.PropTypes (MemberID, ProptypeID).
So, at runtime the list will contain one record for each available proptype and the selected property will be set to yes if that user has selected it. That makes it easy to render the full list in the view...
Its actually quite a bit more complex as there are almost a dozen such lists, but each works the exact same way just with different data, as well as about 200 additional properties that are easier to manage. Only these lists are causing the issue.
Any help appreciated!
Dave
In my view I want to be able to render out the ID of a model property independent of an associated control markup.
So for example, if my model was:
public class Author {
public string Title { get; set; }
public Address Address { get; set; }
}
In my view I would preferably like to be able to use an extension of HtmlHelper to render out the Title property's ID using a lambda. Something like:
#Html.GetClientIdFor(x => x.Title)
And this would output "Title".
In the case of nested objects, I would like:
#Html.GetClientIdFor(x => x.Address.PostCode)
to output "Address_PostCode".
I'm sure I can access the data from the ViewData.TemplateInfo object but from there I'm a little stuck.
Any ideas? I am using MVC4 but I would like a solution that worked for MVC3 too.
Unless I'm misunderstanding your question, there is an HtmlHelper that does exactly what you want: IdFor
#Html.IdFor(x => x.Address.PostCode) // Will return Address_PostCode
Although, that's for MVC4 and up. For MVC3, you will need to roll your own. See this SO.
Hope I spoke correctly in the title. Basically, I have a MVC/Entity Framework application (Code First) and I can clearly see that there's already iteration happening (provided from the scaffolding) to write whatever labels, editor-for, etc. foreach object dynamically created from the user of the application (Create.cshtml), etc.
What I need now is to not have to specify each property name for all the labels, editorfor, etc. I need to have the code just look for each property of the object and display everything but the ID..
I figured this would have been pretty straight forward. But, in addition to the neglecting of such use from the amazing architects of the scaffolding option, I haven't figured it out either. I'm desiring, if possible, to use Entity Framework (or I guess the querying would be LINQ, right?), lambda expressions, in fashion of the rest of the beautiful code.
p.s. I see that there're a few questions out there that are related to this question in some way. But not close enough to what I am asking to solve this problem..
Based on the number of views and no answers, perhaps an example is needed. Below is a class called Item that has a number of properties. Each time the administrator adds another Item, a new instance is created and these properties are set. Well that's fine and dandy, I just don't want to have to specify what all the properties are.. I want to say something like, foreach(property in Item){Html.DisplayNameFor(Item.PropertyName)} and then again, where appropriate, foreach(property in Item){Html.EditorFor(Item.PropertyName)} That way a label and an input box will be created without me having to specify: "Item.Name, Item.Category, Item.Price, Item.Blah" and then if I add more properties I have to go back to my code again and add more stuff...
So, that being said, here's my class:
public class Item
{
public int ID { get; set; }
[Required]
public string ItemName { get; set; }
[DataType(DataType.Date)]
public DateTime AvailableDate { get; set; }
[Required]
public string Category { get; set; }
[Range(1, 10000000)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
}
and now here's where I need the dynamic stuff:
<table>
<tr>
<th>
#Html.DisplayNameFor(foreach(modelProperty in model.Properties)
{modelProperty.PropertyName}
</th>
<tr>
</table>
and
<table>
<tr>
<th>
#Html.DisplayFor(foreach(modelProperty in model.Properties)
{modelProperty.PropertyName}
</th>
</table>
On your item class, you can hide specific columns by turning off scaffolding:
public class Item
{
[ScaffoldColumn(false)]
public int ID { get; set; }
}
Then in your view, you can do this:
#model Item
#Html.EditorForModel()
This will automatically search for these views:
~/Views/{CONTROLLER}/EditorTemplates/Item.cshtml
~/Views/Shared/EditorTemplates/Item.cshtml
(a few others for .vbhtml too)
~/Views/Shared/Object.cshtml
If it none of those exist, it will go to the default object editor. It examines the model metadata to build up the form. The model metadata is built automatically via reflection of the properties.
What you're looking for, doing a loop over an object properties, takes a bit more and will require reflection. In your view, I would strongly recommend against that - too much logic and views should be just simple templates.
As an example, I wrote up a custom Object.cshtml template to match how I lay out forms. This goes in ~/Views/Shared/EditorTemplates/Object.cshtml. It will automatically loop over all the properties (I omitted a couple customized extensions)
<dl>
#foreach (var prop in
ViewData.ModelMetadata.Properties
.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
<dt>#Html.Label(prop.PropertyName)</dt>
<dd>
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName)
</dd>
}
</dl>
You'll find very quickly that the basic editor templates won't cover everything you need. For very basic forms, yes. Complex objects, not so much.
For reference, here's how you'd loop all properties with reflection, but again, bad and redundant idea to actually do this:
#{
// Not good way, with reflection. Have to check for all the various dataannotations
// yourself for each property.
var properties = Model.GetType().GetProperties();
foreach (var property in properties)
{
#property.Name
}
// Better way (sort of), loop the data which is already built
// in the view with metadata properties.
foreach (var property in ViewData.ModelMetadata.Properties)
{
#property.DisplayName
}
}
You can check here for more documentation on what ViewData.ModelMetadata contains.
Isn't that what DisplayForModel is used for?