Morgoth
Morgoth

Reputation: 247

ASP MVC : Binding dynamically generated submit form to multiple objects

I'm doing a class lab which consists of designing a little Crowdfunding website. When the user creates a project, he is asked for how many tiers of rewards he wants for his project. If this amount is superior to 0, he's redirected to the Reward/Create page. The "Create" view has a form in it which is generated according to the amount of tiers the user wants.

The problem is that I have no idea how can I link each instance of the "for" loop to a particular "RewardViewModel" object with one single submit button, and how to pass all these objects to the "post" method in the controller.

Here is the controller "Get" method (I know I'll probably have to change the model binded to the view):

public ActionResult Create(int projectid, int amountOfTiers )
    {
        ViewBag.AmountOfTiers = amountOfTiers;
        ViewBag.ProjectTitle = (from p in context.Projects where p.ProjectId == projectid select p).FirstOrDefault().Title;
        RewardViewModel model = new RewardViewModel { ProjectId = projectid };
        return View(model);
    } 

And here is the view :

@model CrowdFundMe.ViewModels.RewardViewModel

@{
ViewBag.Title = "Create";
}
<body>
@section Login{
    @Html.Action("_Login", "User")
}
<h2>Create</h2>
<p>You are adding rewards for the project @ViewBag.ProjectTitle</p>
@using (Html.BeginForm("Create", "Reward", FormMethod.Post))
  {
    var u = ViewBag.AmountOfTiers;
    for (int i = 0; i < u; i++)
    {
        var tier = i+1;
        @Html.HiddenFor(model => model.ProjectId)

        @Html.LabelFor(model => model.Tier, "Tier")<br />
        @Html.TextBoxFor(model => model.Tier, new {@Value = tier, @readonly="readonly"})
        <br />
        <br />

        @Html.LabelFor(model => model.Title, "Title")<br />
        @Html.TextBoxFor(model => model.Title)
        @Html.ValidationMessageFor(model => model.Title)
        <br />
        <br />

        @Html.LabelFor(model => model.AmountOfItems, "Quantity available (leave blank if unlimited)")<br />
        @Html.TextBoxFor(model => model.AmountOfItems)
        @Html.ValidationMessageFor(model => model.AmountOfItems)
        <br />
        <br />

        @Html.LabelFor(model => model.Description, "Description")<br />
        @Html.EditorFor(model => model.Description)
        @Html.ValidationMessageFor(model => model.Description)
        <br />
        <br />
    }

    <input type="submit" value="Create" />
  }

Upvotes: 2

Views: 754

Answers (1)

user3559349
user3559349

Reputation:

You wanting to create a collection of RewardViewModel therefore your model in the view must be a collection. Currently you only passing one instance of RewardViewModel and then using a for loop to generate multiple form controls with duplicate name attributes (and duplicate id attributes which is invalid html).

Change the GET method to return a collection

public ActionResult Create(int projectid, int amountOfTiers)
{
    List<RewardViewModel> model = new List<RewardViewModel>();
    for(int i = 0; i < amountOfTiers; i++)
    {
        model.Add(new RewardViewModel { ProjectId = projectid });
    }
    ViewBag.ProjectTitle = (from p in context.Projects where p.ProjectId == projectid select p).FirstOrDefault().Title;
    return View(model);
} 

and the view to

@model List<RewardViewModel>
....
@using (Html.BeginForm("Create", "Reward", FormMethod.Post))
{
    for(int i = 0; i < Model.Count; i++)
    {
        @Html.HiddenFor(m => m[i].ProjectId)

        @Html.LabelFor(m => m[i].Title)
        @Html.TextBoxFor(m => m[i].Title)
        @Html.ValidationMessageFor(m => m[i].Title)
        ....
    }
    <input type="submit" value="Create" />
}

Which will correctly name your controls with indexers and will post to

public ActionResult Create(List<RewardViewModel> model)

Side note: remove new { @Value = tier } (never set the value attribute when using the HtmlHelper methods) and its unclear why you have made the Tier property readonly - its value is not set in the controller and cannot be changed in the view so including it in the view seems pointless. If your want to add a consecutive number to the property, then do it in the controller - e.g. model.Add(new RewardViewModel { ProjectId = projectid, Tier = i + 1 });

Note also there is no need to use @Html.LabelFor(model => model.Tier, "Tier") - it can just be @Html.LabelFor(model => model.Tier) if the display text matches the property name.

Upvotes: 1

Related Questions