1

I have a model with the following

    - ModelData: List<ModelData>

With ModelData has the following:

    - Name (string)
    - LanguageId: Guid

And ViewBag has the following:

    - Languages: IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>

And the view has the following:

@for (int i = 0; i < Model.ModelData.Count; i++)
{
<div class="row">
    <div class="form-group">
        <label asp-for="ModelData[i].LanguageId" class="control-label"></label>
        <select asp-for="ModelData[i].LanguageId" asp-items="@ViewBag.Languages" class="form-control">
        </select>
        <span asp-validation-for="ModelData[i].LanguageId" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="ModelData[i].Name" class="control-label"></label>
        <input asp-for="ModelData[i].Name" class="form-control" />
        <span asp-validation-for="ModelData[i].Name" class="text-danger"></span>
    </div>
    @if (i > 0)
    {
        <div class="col-md-4">
            <div class="form-group">
                <button name="RemoveDataItem" value="@i.ToString()" class="btn btn-primary">Remove</button>
            </div>
        </div>
    }
</div>
}

<input type="submit" name="AddDataItem" value="Add Item" class="btn btn-primary" />


<input type="submit" value="Create" class="btn btn-primary" />

And the controller as the following:

public async Task<IActionResult> CreateAsync(CreateModel model, string addDataItem, string removeDataItem)
        {
            if (addDataItem != null)
            {
                await FillViewBag();
                model.ModelData.Add(new ModelData());
                return View(model);
            }
            else if (removeDataItem != null)
            {
                await FillViewBag();
                int itemIndex = int.Parse(removeDataItem);
                model.ModelData.RemoveAt(itemIndex);
                return View(model);
            }
            if (!ModelState.IsValid)
            {
                await FillViewBag();
                return View(model);
            }

            // Save
        }

And it works great, however I have a problem as the following:

Let's say i pressed the add button two times so now I have three records on ModelData and I entered a value in all textboxes and selected values in all select list, then I pressed remove next to the second row, so it goes to the controller action, the method removes the data of the correct index, and returns to the view, so Now I should find two rows, first with the data that was entered in the first row, and second with the data that was entered in the third row (because the second row is removed), however, what actually happens is that I end up with the data of the first two rows not the first and the third.

Any help is appreciated considering I did the following:

  • I validated that the item that is removed is the corect one (the second item), but the value is not bound correctly.
  • I added this attribute to the textbox value="@Model.ModelData[i].Name", and it worked correctly but I think this is not the correct way to solve this issue, also I did not find a similar attribute for the select tag.

Edit:

  • I also managed to add static Id for the input fields of each row, but it didn't help

Edit: The problem is that the index is changed after the second row is removed, so the index of the third row (originally was 2) became 1 after removing the second row, and thus it now has the previous name attribute of second row "ModelData[1].Name" and not "ModelData[2].Name" and I think this is the problem which makes the browser keeps the previous value of the second row

3 Answers 3

5

For anyone who is concerned, I found the solution to this issue which is to add the following line before returning the view:

ModelState.Clear();
Sign up to request clarification or add additional context in comments.

Comments

0

add index value to each item:

<input type="hidden" name="ModelData.index" value="@i">

update your view code like this:

@for (int i = 0; i < Model.ModelData.Count; i++)
{
<div class="row">
    <input type="hidden" name="ModelData.index" value="@i">
    <div class="form-group">
        <label asp-for="ModelData[i].LanguageId" class="control-label"></label>
        <select asp-for="ModelData[i].LanguageId" asp-items="@ViewBag.Languages" class="form-control">
        </select>
        <span asp-validation-for="ModelData[i].LanguageId" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="ModelData[i].Name" class="control-label"></label>
        <input asp-for="ModelData[i].Name" class="form-control" />
        <span asp-validation-for="ModelData[i].Name" class="text-danger"></span>
    </div>
    @if (i > 0)
    {
        <div class="col-md-4">
            <div class="form-group">
                <button name="RemoveDataItem" value="@i.ToString()" class="btn btn-primary">Remove</button>
            </div>
        </div>
    }
</div>

}

3 Comments

Tried and still the same, as I said in my edit in the question, The problem is that the index is changed after the second row is removed, so the index of the third row (originally was 2) became 1 after removing the second row, and thus it now has the previous name attribute of second row "ModelData[1].Name" and not "ModelData[2].Name" and I think this is the problem which makes the browser keeps the previous value of the second row
you don't need to change index of row items after remove
I do not "change index of row items after remove", it's changed automatically since the list has now two items instead of three!!
0

Where's your source data? What you have done is just change the model parmeter.

Also, I can reproduce your problem, when I directly return View after remove the specify record. It can be fixed by using RedirectToAction

I made a demo based on your codes, you can refer to the below codes:

Controller:

public static CreateModel createModel = new CreateModel
{
    ModelDatas = new List<ModelData>
        {
            new ModelData{ LanguageId = 1, Name = "a"},
            new ModelData{ LanguageId = 2, Name = "b"},
            new ModelData{ LanguageId = 3, Name = "c"}
        }
};

public IActionResult Create()
{
    FillViewBag();
    return View(createModel);
}

[HttpPost]
public IActionResult Create(CreateModel model, string addDataItem, string removeDataItem)
{
    if (addDataItem != null)
    {
        FillViewBag();
        createModel.ModelDatas.Add(new ModelData());
        return RedirectToAction("Create");
    }
    else if (removeDataItem != null)
    {
        FillViewBag();
        int itemIndex = int.Parse(removeDataItem);
        createModel.ModelDatas.RemoveAt(itemIndex);
            
        return RedirectToAction("Create");
    }
    if (!ModelState.IsValid)
    {
        FillViewBag();
        return RedirectToAction("Create");
    }
    return View();
}

View:

@model CreateModel

<form asp-action="Create" asp-controller="Test" method="post">
    @for (int i = 0; i < Model.ModelDatas.Count; i++)
    {
        <div class="row">
            <div class="form-group">
                <label asp-for="ModelDatas[i].LanguageId" class="control-label"></label>
                <select asp-for="ModelDatas[i].LanguageId" asp-items="@ViewBag.Languages" class="form-control">
                </select>
                <span asp-validation-for="ModelDatas[i].LanguageId" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ModelDatas[i].Name" class="control-label"></label>
                <input asp-for="ModelDatas[i].Name" class="form-control" />
                <span asp-validation-for="ModelDatas[i].Name" class="text-danger"></span>
            </div>
            @if (i >= 0)
            {
                <div class="col-md-4">
                    <div class="form-group">
                        <button name="RemoveDataItem" value="@i.ToString()" class="btn btn-primary">Remove</button>
                    </div>
                </div>
            }
        </div>
    }

    <input type="submit" name="AddDataItem" value="Add Item" class="btn btn-primary" />


    <input type="submit" value="Create" class="btn btn-primary" />
</form>

Result:

enter image description here

1 Comment

I really appreciate your contribution, however what you have done is to create a static model which will be shared across all requests and this is of course not correct, in my scenario I need the model to be different in every request, and of course I can't use RedirectToAction() as it will go to the original action that creates a model with only one data item every time.

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.