AhmedMSedeek
AhmedMSedeek

Reputation: 51

ASP .Net MVC Core binds wrong value in model list

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:

Edit:

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

Upvotes: 1

Views: 574

Answers (3)

AhmedMSedeek
AhmedMSedeek

Reputation: 51

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();

Upvotes: 3

mj1313
mj1313

Reputation: 8459

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

Upvotes: 0

Ali Panahian
Ali Panahian

Reputation: 315

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>

}

Upvotes: 0

Related Questions