MVC post a list of complex objects

前端 未结 5 1874
眼角桃花
眼角桃花 2020-11-30 06:41

I have a FeedbackViewModel that contains a list of questions:

public class FeedbackViewModel
{
    public List Questions { get; set;         


        
相关标签:
5条回答
  • 2020-11-30 07:26

    Thanks for pointing me in the right direction with this post. I was struggling to get the syntax right for binding a non-sequential IDictionary<string, bool> object. Not sure this is 100% correct, but this Razor code worked for me:

    <input type="hidden" name="MyDictionary.Index" value="ABC" />
    <input type="hidden" name="MyDictionary[ABC].Key" value="ABC" />
    @Html.CheckBox(name: "MyDictionary[ABC].Value", isChecked: Model.MyDictionary["ABC"], htmlAttributes: null)
    

    If you need a checkbox, be sure to use Html.CheckBox instead of a standard HTML checkbox. The model will blow up if a value is not provided, and Html.CheckBox generates a hidden field to ensure a value is present when the checkbox is not checked.

    0 讨论(0)
  • 2020-11-30 07:28

    After much research I've found two solutions:

    1. One is to write HTML that has hardcoded Id's and Names
    2. Two is to convert your ICollection/IEnumerable to an Array or List (i.e IList something with an 'index'), and have an Array object in your BindingModel in your Controller POST Action.

    Thanks to Phil Haack's (@haacked) 2008 blog post http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ Which is still relevant to how the default ModelBinder works today for MVC. (NB: the links in Phil's article to sample porject and extension methods are broken)

    HTML snippet that inspired me:

    <form method="post" action="/Home/Create">
        <input type="hidden" name="products.Index" value="cold" />
        <input type="text" name="products[cold].Name" value="Beer" />
        <input type="text" name="products[cold].Price" value="7.32" />
    
        <input type="hidden" name="products.Index" value="123" />
        <input type="text" name="products[123].Name" value="Chips" />
        <input type="text" name="products[123].Price" value="2.23" />
    
        <input type="submit" />
    </form>
    

    Post array looks a bit like:

    products.Index=cold&products[cold].Name=Beer&products[cold].Price=7.32&products.Index=123&products[123].Name=Chips&products[123].Price=2.23
    

    Model:

    public class CreditorViewModel
    {
        public CreditorViewModel()
        {
            this.Claims = new HashSet<CreditorClaimViewModel>();
        }
        [Key]
        public int CreditorId { get; set; }
        public string Comments { get; set; }
        public ICollection<CreditorClaimViewModel> Claims { get; set; }
        public CreditorClaimViewModel[] ClaimsArray { 
            get { return Claims.ToArray(); }
        }
    }
    
    public class CreditorClaimViewModel
    {
        [Key]
        public int CreditorClaimId { get; set; }
        public string CreditorClaimType { get; set; }
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:N2}")]
        public Decimal ClaimedTotalAmount { get; set; }
    }
    

    Controller GET:

    public async Task<ActionResult> Edit(int id)
        {
            var testmodel = new CreditorViewModel
            {
                CreditorId = 1,
                Comments = "test",
                Claims = new HashSet<CreditorClaimViewModel>{
                    new CreditorClaimViewModel{ CreditorClaimId=1, CreditorClaimType="1", ClaimedTotalAmount=0.00M},
                    new CreditorClaimViewModel{ CreditorClaimId=2, CreditorClaimType="2", ClaimedTotalAmount=0.00M},
                }
            };
            return View(model);
        }
    

    Edit.cshtml:

    @Html.DisplayNameFor(m => m.Comments)
    @Html.EditorFor(m => m.Comments)
    
    <table class="table">
        <tr>
            <th>
                @Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().CreditorClaimType)
            </th>
            <th>
                @Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().ClaimedTotalAmount)
            </th>
        </tr>        
    <!--Option One-->
    @foreach (var item in Model.Claims)
    {
        var fieldPrefix = string.Format("{0}[{1}].", "Claims", item.CreditorClaimId);
        <tr>
            <td>
                @Html.DisplayFor(m => item.CreditorClaimType)
            </td>
            <td>
            @Html.TextBox(fieldPrefix + "ClaimedTotalAmount", item.ClaimedTotalAmount.ToString("F"),
            new
            {
                @class = "text-box single-line",
                data_val = "true",
                data_val_number = "The field ClaimedTotalAmount must be a number.",
                data_val_required = "The ClaimedTotalAmount field is required."
            })
            @Html.Hidden(name: "Claims.index", value: item.CreditorClaimId, htmlAttributes: null)
            @Html.Hidden(name: fieldPrefix + "CreditorClaimId", value: item.CreditorClaimId, htmlAttributes: null)
            </td>
        </tr>
        }
    </table>    
    <!--Option Two-->
    @for (var itemCnt = 0; itemCnt < Model.ClaimsArray.Count(); itemCnt++)
    {
        <tr>
            <td></td>
            <td>
                @Html.TextBoxFor(m => Model.ClaimsArray[itemCnt].ClaimedTotalAmount)
                @Html.HiddenFor(m => Model.ClaimsArray[itemCnt].CreditorClaimId)
        </td></tr>
    }
    

    Form is processed in the Controller:

    Post Model:

    public class CreditorPostViewModel
    {
        public int CreditorId { get; set; }
        public string Comments { get; set; }
        public ICollection<CreditorClaimPostViewModel> Claims { get; set; }
        public CreditorClaimPostViewModel[] ClaimsArray  { get; set; }
    }
    
    public class CreditorClaimPostViewModel
    {
        public int CreditorClaimId { get; set; }
        public Decimal ClaimedTotalAmount { get; set; }
    }
    

    Controller:

    [HttpPost]
        public ActionResult Edit(int id, CreditorPostViewModel creditorVm)
        {
            //...
    
    0 讨论(0)
  • 2020-11-30 07:29

    I use this code maybe its can help

    <input type="hidden" name="OffersCampaignDale[@(item.ID)].ID" value="@(item.ID)" />
    
    @Html.Raw(Html.EditorFor(modelItem => item.NameDale, new { htmlAttributes = new { @class = "form-control" } })
    .ToString().Replace("item.NameDale", "OffersCampaignDale[" + item.ID+ "].NameDale").Replace("item_NameDale", "NameDale-" + item.ID))
    @Html.ValidationMessageFor(modelItem => item.NameDale, "", new { @class = "text-danger" })
    
    0 讨论(0)
  • 2020-11-30 07:31

    Make sure you are rendering your view in order so that Model.Questions[i] renders in order.

    For example, Model.Questions[0], Model.Questions[1], Model.Questions[2]. I noticed that if the order is not correct mvc model binder will only bind the first element.

    0 讨论(0)
  • 2020-11-30 07:32

    Using Razor you can implement the for loop using a dictionary as follows without making changes to your object:

    @foreach (var x in Model.Questions.Select((value,i)=>new { i, value }))
    {
         if (Model.Questions[x.i].QuestionType == "Single")
         {
              @Html.EditorFor(modelItem => (modelItem.Questions[x.i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
         }
       ...
    }
    

    The collection needs to be either a List or Array for this to work.

    0 讨论(0)
提交回复
热议问题