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.