2

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" />
  }

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

It worked perfectly. Thanks for all the details. To answer your own questions about my code, I set the tier value and set it as readonly because the user should not be able to edit the tier level of the reward he is encoding. If he chose to have 2 level of rewards, he must have "tier 1" and "tier 2" and not be able to edit that in "tier 5" and "tier 8" for example.
Just ensure you do not set the value attribute in the view. Set it in the controller before you pass the model to the view as I noted in the 2nd last paragraph
I added "Tier = i+1" in the creation of each new object in the Create get method in the controller instead of setting the value in the view. Once again thanks for your precious advices.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.