MVC Collection Missing items on Post

不羁的心 提交于 2019-12-12 06:01:12

问题


I've recently run into some behaviour with MVC4 that I'm hoping to gain some perspective on. The behaviour in my actual project uses more complex objects, but I was able to replicate it with simple objects, which is what I'm using in this question. My situation is as follows:

  1. I have a View that uses a Collection of strings as its model.
  2. I'm calling "EditorFor" to display each string in the collection.
  3. In the controller, I am populating the model with 7 string items and passing it to the view.
  4. The view renders all 7 items.
  5. I'm using jquery to remove the 2nd item in the collection from the DOM.
  6. I click submit to post back.
  7. I check the contents of my posted back model and it only contains the first item in the list. I expected to have 6 items in the list (7 minute the one I removed from the DOM).

The code for the described situation is as follows:

View

@model ICollection<string>

@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
    @Html.EditorFor(x => x);
    <input id="btnSubmit" type="submit" value="Submit" />
}

Controller

[HttpGet]
public ActionResult Index()
{
     List<string> vm = new List<string>
     {
         "one",
         "two",
         "threE",
         "four",
         "five",
         "six",
         "seven"
     };

     return View(vm);
}

[HttpPost]
public ActionResult Index(List<string> vm)
{
    return View(vm);
}

Thanks to anyone who helps out with this. Looking forward to increasing my understanding of MVC!


回答1:


it's list binding issue.
this is the greate article about list binding in mvc: Model Binding To A List




回答2:


That is an indexing issue.

@model ICollection<string>    
@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm("Index", "Home", FormMethod.Post)) {
    @for(int index = 0; index < Mode.Count; i++) {
        @Html.EditorFor(x => x[index])
    }
    <input id="btnSubmit" type="submit" value="Submit" />
}

The tricky part would be managing the remaining indexes if the plan is to remove items.




回答3:


Using the information in the answer that Masound posted, I was able to create a solution to this problem. It turns out the problem is that I was using a sequential collection where each index relies on the previous one being present. So when I removed one element from the collection, I lost the link to the other items that proceed it.

The solution is to use a non sequential collection where the index is stored as a hidden field in the DOM. Using this method, MVC knows where each index lies and can recreate the collection on post with all the elements.

A description of the solution is as follows:

  1. I have a View that uses a Collection of Test objects as its model.
  2. I have a helper class that outputs the index of an object in a collection.
  3. I'm calling "EditorFor" to display each Test object and its index within the collection.
  4. In the controller, I am populating the model with 7 string items and passing it to the view.
  5. The view renders all 7 items.
  6. I'm using jquery to remove the 2nd item in the collection from the DOM.
  7. I click submit to post back.
  8. I check the contents of my posted back model and it only contains 6 items, which is what I expect.
  9. I am doing a re-direct to the Get method to refresh the binding of the model state. I noticed behavior where indexing would bind to the wrong item in the collection. So, if you are planning to reload the same view, then I recommend implementing this solution to avoid unexpected behaviours.

The code for this solution is as follows:

Test.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace WebApplication2.Models
{
    public class Test
    {
        [Required(ErrorMessage="Required")]
        public string str {get; set;}
    }
}

Test.cshtml

@model WebApplication2.Models.Test
@using WebApplication2.Helpers

@{
    ViewBag.Title = "Test";
}

<div id="@Model.str">
    @Html.TextBoxFor(x => x.str)
    @Html.ValidationMessageFor(x => x.str)
    @Html.HiddenIndexerInputForModel()
</div>

Index.cshtml

@model ICollection<WebApplication2.Models.Test>

@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
    @Html.EditorFor(x => x)
    <input id="btnSubmit" type="submit" value="Submit" />
}

Controller

[HttpGet]
public ActionResult Index()
{
    List<Test> vm = (List<Test>)Session["Test"];

    if (vm == default(List<Test>))
    {
        vm = new List<Test>
        {
            new Test { str="one"},
            new Test { str="two"},
            new Test { str="three"},
            new Test { str="four"},
            new Test { str="five"},
            new Test { str="six"},
            new Test { str="seven"}
         };
         Session.Add("Test", vm);
    }                    

    return View(vm);
}

[HttpPost]
public ActionResult Index(List<Test> vm)
{
    Session.Add("Test", vm);
    return RedirectToAction("Index");
}

HtmlHelper.cs - Helper was taken from here: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

public static class ListModelBindingExtensions
{
    static Regex _stripIndexerRegex = new Regex(@"\[(?<index>\d+)\]", RegexOptions.Compiled);

    public static string GetIndexerFieldName(this TemplateInfo templateInfo)
    {
        string fieldName = templateInfo.GetFullHtmlFieldName("Index");
        fieldName = _stripIndexerRegex.Replace(fieldName, string.Empty);
        if (fieldName.StartsWith("."))
        {
            fieldName = fieldName.Substring(1);
        }
        return fieldName;
    }

    public static int GetIndex(this TemplateInfo templateInfo)
    {
        string fieldName = templateInfo.GetFullHtmlFieldName("Index");
        var match = _stripIndexerRegex.Match(fieldName);
        if (match.Success)
        {
            return int.Parse(match.Groups["index"].Value);
        }
        return 0;
    }

    public static MvcHtmlString HiddenIndexerInputForModel<TModel>(this HtmlHelper<TModel> html)
    {
            string name = html.ViewData.TemplateInfo.GetIndexerFieldName();
            object value = html.ViewData.TemplateInfo.GetIndex();
            string markup = String.Format(@"<input type=""hidden"" name=""{0}"" value=""{1}"" />", name, value);
            return MvcHtmlString.Create(markup);
    }
}

Thanks to everyone who helped in the arrival of this solution!



来源:https://stackoverflow.com/questions/45133856/mvc-collection-missing-items-on-post

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!