1

i am actually try to populate dynamically html code in Asp.Net Core Razor and i want to use something like this:

@model Aktienverwaltung.Models.ProtokollDividenden

foreach (var item in Model)
{
   <div class="form-group row">
       <label asp-for="@item" class="col-lg-3 control-label text-lg-right pt-2" for="inputRounded"></label>
       <div class="col-lg-6">
           <input asp-for="@item" class="form-control input-rounded" id="inputRounded" />
       </div>
       <span asp-validation-for="@item" class="text-danger"></span>
   </div>
}

this is the part of my Controller:

public async Task<IActionResult> Edit(Guid? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var protokollDividenden = await _context.ProtokollDividenden.FindAsync(id);
    if (protokollDividenden == null)
    {
        return NotFound();
    }
    // return View(await _context.ProtokollDividenden.ToListAsync());
    return View(protokollDividenden);
}

and the Model:

public partial class ProtokollDividenden
    {
        public Guid Id { get; set; }
        public string Aktie { get; set; }
        public string Depotname { get; set; }
        public string Wkn { get; set; }
        public string Kuerzel { get; set; }
        public double Dividende { get; set; }
        public DateTime Datum { get; set; }
        public bool BereitsVerarbeitet { get; set; }
    }

Everything i tried goes wrong because i cant enumerate the Model or something else. I tried it as List too (see uncommented area in Controller), but i can`t get the foreach loop to work - did anyone have an idea ?

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
fahr
  • 21
  • 1
  • 7
  • 1
    Possible duplicate of [Model Binding to a List MVC 4](https://stackoverflow.com/questions/15375800/model-binding-to-a-list-mvc-4) – Owen Pauling Mar 08 '19 at 14:04
  • 2
    Just don't use `foreach`. You can't enumerate a *single* value. You enumerate lists, arrays, dictionaries etc. `foreach (var item in Model)` should throw an exception. If you know you have a single value there's no reason to use `foreach` – Panagiotis Kanavos Mar 08 '19 at 14:05
  • BTW your HTML doesn't try to bind the model's fields at all. Were you trying to bind all fields in a loop? – Panagiotis Kanavos Mar 08 '19 at 14:08

1 Answers1

0

First, you can only enumerate an enumerable, i.e. IEnumerable<T>, List<T>, etc. Right now, you have just a single item as your model, so the foreach will obviously fail.

Assuming you do return an enumerable model, then your next issue is model binding. In order to properly bind the data you post back to a param on your action, list types must be named according to the format of CollectionProperty[N].Property, where N is an index. In order for things like tag helpers to generate the right name attributes on your form inputs, they need a full model expression. You can think of a model expression as a map to a particular property you're trying to bind to.

Let's say you asked me for directions to a place. You would ideally need the steps to begin from where you're at right now. If I began my directions from some other place, that wouldn't be of much help, because then you would need directions to that place first, before you could follow my directions to your final destination.

The concept is similar here. Using a foreach and creating an item variable, basically removes the first part of the directions. The tag helpers know how to get from item to the property you're binding to, but they don't know how to get to item. As such, you'll end up with names like item.Foo, which will not be able to be bound to anything meaningful. Instead, you need to use a for loop and the full model expression using the index in the loop:

@for (var i = 0; i < Model.Count; i++) // assuming `Model` is a list
{
    <input asp-for="@Model[i].Foo" />
}

It's worth noting that the value of asp-for above is a special construction required because Model itself is the list you're iterating over. If, instead it was a list property on model, you would simply use asp-for="ListProperty[i].Foo". The @Model part is there simply because some actual member must be used before the indexing notation. I've seen people get confused by this and start trying to affix @Model before everything, i.e. @Model.ListProperty[i].Foo.

EDIT

OK. I think I know where you're going now, but your question was way off the mark in conveying that. If you're looking for some way to automatically have inputs generated for you for every member of the model without having to go member by member, then the answer is, well, murky.

There's Html.EditorFor and Html.EditorForModel (which I think still exists in ASP.NET Core... I don't use them personally). Both are "templated helpers", which mean they rely on templates to determine how they handle what's passed into them. Since you're working with your model directly here, you'd want Html.EditorForModel, which you'd use literally via the following in place of all your other current view code:

@Html.EditorForModel()

Out of that box, that's going to look at each member and generate a set of inputs and labels for each, according to the specific types of the members, which will end up looking a little something like:

<label for="Id">Id</label>
<input id="Id" name="Id" value="" />

<label for="Aktie">Aktie</label>
<input id="Aktie" name="Aktie" value="" />

<label for="Depotname">Depotname</label>
<input id="Depotname" name="Depotname" value="" />

...

There are some obvious issues with this. It doesn't have any of your Bootstrap classes or structure, so it's going to look awful. It's very simple in it's interpretation of type to input mapping. For example, Id is something that should actually probably not be there at all or if it is present, it should be an a hidden input. Aktie may be a long form text field, but you're always going to get an input, rather than something like textarea. Maybe Depotname should be a select with some list of possible choices, but again, you're only ever going to get a basic text input. The list goes on.

Some of these can be fixed easier than others. To have a hidden input, apply the [HiddenInput] attribute to the property in your class. The DataType attribute can be applied to give some more context. For example, if you want a textarea, you can apply [DataType(DataType.MultilineText)] to the property in your class. If you need to change the label text, you can do that with [Display(Name = "My Label Text")].

For more complex things, that's where templates start to come in. For example, you can create a view like Views\Shared\EditorTemplates\String.cshtml and inside add:

@model string

<div class="form-group row">
   <label asp-for="@Model" class="col-lg-3 control-label text-lg-right pt-2" for="inputRounded"></label>
   <div class="col-lg-6">
       <input asp-for="@Model" class="form-control input-rounded" id="inputRounded" />
   </div>
   <span asp-validation-for="@Model" class="text-danger"></span>

Then, this will be used for any string type member. As you might imagine, you can add other such views like DateTime.cshtml, Int32.cshtml, etc., which will each be used for their respective types. You can also add templates for any of the members of that DataType enum, so you can have MultilineText.cshtml, EmailAddress.cshtml, etc. Finally, there is a UIHint attribute that can be applied to specify any template. For example, if you applied [UIHint("Foo")] to a property, then Foo.cshtml would be used.

You can also create templates for your own custom types. So, for example, you could have a ProtokollDividenden.cshtml and then this view would actually be used as the template for that class, which then means it would be used for your call to @Html.EditorForModel().

However, with all of this, it's important to note that once you add a custom template, you are 100% responsible for that now. In other words, if you were to add a view for your whole model, then that would take over, and Html.EditorForModel wouldn't actually generate the individual fields any more for you, basically bringing you back to the same problem you started with. Instead, it would simply dump your custom template and call it a day.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • thx Chris, but this still dosnt work, the Model.Count is ever 1 and should be 8 items - look at the Model please !? I need the fields in the Model and not the Model :-) – fahr Mar 08 '19 at 14:30
  • I'm not sure where you getting "8" from, though I'm guessing because your model has 8 members, that's the source. If that is the case, then you *really* don't understand anything that's going on here. Members of a class are not enumerable. You have to access them individually, i.e. ``. This is basic C# 101. Actually, this is basic programming 101, honestly. You need to take a step back and actually get your bearings with how basic data structures work in programming languages. – Chris Pratt Mar 08 '19 at 14:35
  • Yes, i am a newbie in C# but i know about that i cant access the member, that was my question - ho can i populate the HTML with the class members - i think there should be a way, isn`t it ? I dont know how to solve this... – fahr Mar 08 '19 at 14:41