25

I have a painfully simple view model

public class TellAFriendViewModel
{
    public string Email1 { get; set; }
    public string Email2 { get; set; }
    public string Email3 { get; set; }
    public string Email4 { get; set; }
    public string Email5 { get; set; }
}

And then the corresponding inputs on my view, but I'm wondering if there is a better way (such as a loop) to write similar TextBoxes to my view:

@using (Html.BeginForm()){
    @Html.AntiForgeryToken()

    @Html.TextBoxFor(vm => vm.Email1)
    @Html.TextBoxFor(vm => vm.Email2)
    @Html.TextBoxFor(vm => vm.Email3)
    @Html.TextBoxFor(vm => vm.Email4)
    @Html.TextBoxFor(vm => vm.Email5)
}
Nick Brown
  • 1,167
  • 1
  • 21
  • 38
  • 2
    You could also consider condensing your input to an array of email addresses and expanding dynamically on the client or looping to a preset iteration in the razor code. `public string[] Emails ...` etc. – Quintin Robinson Mar 28 '12 at 23:01

7 Answers7

38

You should access

ViewData.ModelMetadata.Properties. No reason to double the reflection effort, plus it figures out DataAttributes metadata for you.

@foreach(var property in ViewData.ModelMetadata.Properties)
{
    <div class="editor-line">
        <label>@(property.DisplayName??property.PropertyName)</label>
        @Html.Editor(property.PropertyName)
    </div>
}
moribvndvs
  • 42,191
  • 11
  • 135
  • 149
  • i had a question here, what is convenient using foreach propertyinfo or by use (same as the question code) individual property manually ? and if foreach loop than i have doubt it may increase cost of execution/process. – asharajay Oct 23 '12 at 08:55
  • 2 questions: is there a way to determain the order of the properties? And what are thos two question marks property.DisplayName??property.PropertyName – Sagiv b.g Jul 06 '15 at 18:21
  • found an answer to my second question http://stackoverflow.com/questions/446835/what-do-two-question-marks-together-mean-in-c – Sagiv b.g Jul 06 '15 at 18:41
  • @Sag1v `??` in C# is called a [null coalescing operator](https://msdn.microsoft.com/en-us/library/ms173224.aspx). Basically we're saying "emit the `property.DisplayName` here if it's not null; if it is null, emit `property.PropertyName`". I've not tried it so I don't know if it will work, but you might be able to use the `DisplayAttribute.Order` property. See https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute.order.aspx – moribvndvs Jul 06 '15 at 21:38
  • @Sag1v If the properties aren't already pre-sorted in the `ViewData.ModelMetadata.Properties` collection, you can always try and reorder the collection right then and there yourself. Perhaps just use the LINQ OrderBy extension: `@foreach(var property in ViewData.ModelMetadata.Properties.OrderBy(m => m.Order))`... – moribvndvs Jul 06 '15 at 21:42
  • Great answer. This simplifies the logic immensely, thank you. – Hans Vonn Jul 18 '17 at 20:05
12

You should consider using an array.

However, if you wanted to go with reflection, it would look like this:

@foreach (var prop in Model.GetType().GetProperties())
{
    @(Html.TextBox(prop.Name, prop.GetValue(Model, null)))
}
Xavier Poinas
  • 19,377
  • 14
  • 63
  • 95
  • Your syntax for `PropertyInfo.GetValue` is incorrect. Does `Model.GetType` not work in Razor? If it does then it would generally be preferred. – M.Babcock Mar 28 '12 at 23:12
  • 1
    You're right. (the previous syntax does work on .NET 4.5 as a new overload of `GetValue` is introduced) – Xavier Poinas Mar 28 '12 at 23:31
  • Really!? I've just started tinkering in 4.5 and mostly with legacy 4 code so it's good to know that I can remove my extension method once it gets released. – M.Babcock Mar 28 '12 at 23:34
  • Yep, [here it is](http://msdn.microsoft.com/en-us/library/hh194385%28v=vs.110%29.aspx) :) – Xavier Poinas Mar 28 '12 at 23:44
  • +1 - though apparently there is a better way to solve this question. – M.Babcock Mar 28 '12 at 23:46
3

Unless i'm missing something, I don't know why no-one has suggested this. Why is everyone looping and/or using reflection??

public class TellAFriendViewModel
{
    public ICollection<EmailViewModel> Emails { get; set; } // populate 5 of them in ctor or controller
}

public class EmailViewModel
{
    public string Email { get; set; }
}

View:

@using (Html.BeginForm()){
    @Html.AntiForgeryToken()
    @Html.EditorFor(model => model.Emails)
}

EditorTemplates\EmailViewModel.cshtml

@Html.TextBoxFor(model => model.Email)

Loops are NEVER required in MVC. I repeat. NEVER

RPM1984
  • 72,246
  • 58
  • 225
  • 350
  • 4
    Loops are NEVER required in MVC. I repeat. NEVER - Not so. – DavidB Feb 28 '13 at 10:57
  • @DavidB - i stand by my statement. Name an example where it is required, and i'll show you how it can be avoided. – RPM1984 Feb 28 '13 at 21:11
  • Cool thanks for the response, does this include helpers not having a for loop? Ill try and come up with a challenge as I thought this was quite a contentious statement to make without a reason. I do like being proved wrong so will try and post an example when I have time – DavidB Mar 01 '13 at 08:28
  • I know it's contentious, but i just hate loops and there is always a way around them. Loops in HTML helpers is fine. I'm just against them in markup. Like Travis' answer below - absolute code bloat. – RPM1984 Mar 01 '13 at 21:53
  • 3
    +1 For the only example I could find supporting `ICollection<>`. Also, "Only the Sith deal in absolutes" (hyuck :P) – Albert Bori Mar 07 '15 at 20:22
  • @DavidB had a chance to come up with a challenge yet? :) – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Jun 15 '15 at 11:23
  • 1
    @MattBaker If you're stuck with MVC3 (as per this question) then the challenge could be to get a big grid of edit boxes on the page using EditorFor - there was a bug in MVC3 with the caching of the partial view templates which means that performance drops off exponentially as the number of EditorFor calls increases - which means with 1000 editors it's well worth swapping it out for a loop of hand written inputs. – Kaine Nov 17 '16 at 17:24
2

This is the accepted way to do it

foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => !pm.HideSurroundingHtml && pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) {

<div class="form-group">

    <label>
        @prop.GetDisplayName() 
        @if (prop.IsRequired)
        {
            <span class="required">*</span>
        }
    </label>
    @Html.Editor(prop.PropertyName)

    @Html.ValidationMessage(prop.PropertyName, new {@class = "help-block"})
</div>

}

Rod Johnson
  • 2,387
  • 6
  • 30
  • 48
0

you could use reflection over the properties, or a simple for loop to generate the same HTML as is generated in your solution, but what's your goal?
For simplicity, what you have wins.

Reflection

 foreach (var prop in typeof(whatever).GetProperties(BindingFlags.Instance | BindingFlags.Public))
 {
    @Html.TextBox(prop.Name, prop.GetValue(Model));
 }

Loop

var numberProperties = 5; // you could also do typeof(whatever).GetProperties(BindingFlags.Instance | BindingFlags.Public).Count();
@for(var i = 0; i < numberProperties; i++){
 <input type="text" name="Email@i" id="Email@i"/>
}
Rob Rodi
  • 3,476
  • 20
  • 19
  • I'm just curious how I would loop through the model to display them. In this instance I probably will stick with what is provided, but say I had 90 email inputs, I probably wouldn't want to write them. – Nick Brown Mar 28 '12 at 23:07
  • @NickBrown If you had 90 emails and you were perpetuating the design in place you may want to consult someone to help you fix it, or just go with an array design to begin with. – Quintin Robinson Mar 28 '12 at 23:12
0

You can use Reflection to loop through each property of your model...as below

Type type = Model.GetType(); // Model is the object you are binding with in your view
PropertyInfo[] properties = type.GetProperties();
foreach (var property in properties)
{
    // Code to create your text box for the property
}

Hope this helps...

NiK
  • 1,827
  • 1
  • 20
  • 37
0

Perhaps like this?

public class TellAFriendViewModel
{
 List<string> Emails { get; set; }

 public TellAFriendViewModel()
 {
  Emails = new List<string>(5);
 }
}

@using (Html.BeginForm()){
 @Html.AntiForgeryToken()

 @for(int count = 0 ; count < model.Emails.Count; count++)
 {
  @Html.TextBoxFor(vm => vm.Emails[count])
 }
}
Travis J
  • 81,153
  • 41
  • 202
  • 273