Model binding doesn't work when multiple instances of the same partial view are called dynamically with ajax.actionlink in MVC 5

自古美人都是妖i 提交于 2019-12-24 17:12:46

问题


I'm building a restaurant reservations system. When the user creates a reservation they can also decided what furniture will be used with the reservation. For this I created a partial view, so every time the user click on "add furniture" the partial view is loaded with ajax where the user can then specify what furniture and how many. They can add as many types of furniture as they want, meaning they can call the partial view as many times as they want. My problem comes in with the model binder, the model binder then have bind all those partial view instances to a list object of type "BistroReservations_ReservationsFurniture". Please help how to make the model binder work.

MainView (Please have a look at the bottom for the ajax.actionlink call "Add Furniture"

@using (Html.BeginForm()) 

{ @Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>BistroReservations_Reservation</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(model => model.DateOfArrival, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.DateOfArrival, new { htmlAttributes = new { @class = "form-control datecontrol" } })
            @Html.ValidationMessageFor(model => model.DateOfArrival, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.BistroReservations_ShiftID, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("BistroReservations_ShiftID", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.BistroReservations_ShiftID, "", new { @class = "text-danger" })
        </div>
    </div>

<div class="form-group">
    @Html.LabelFor(model => model.BistroReservations_GuestID, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-6">
        @Html.DropDownList("BistroReservations_GuestID", null, htmlAttributes: new { @class = "form-control" })     
        @Html.ValidationMessageFor(model => model.BistroReservations_GuestID, "", new { @class = "text-danger " })

        @Ajax.ActionLink("Create a New Guest", "NewGuest", new AjaxOptions
       {
           HttpMethod = "GET",
           UpdateTargetId = "NewGuest",
           InsertionMode = InsertionMode.ReplaceWith,                              
       })            
    </div>
</div>       

    <div id="NewGuest" class="form-group">
    </div>
    <br />

    <div class="form-group">
        @Html.LabelFor(model => model.ArrivalTime, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("ArrivalTime", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.ArrivalTime, "", new { @class = "text-danger" })
        </div>
    </div>   

    <div class="form-group">
        @Html.LabelFor(model => model.LocationID, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("LocationID", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.LocationID, "", new { @class = "text-danger" })
        </div>
    </div>   

    <div class="form-group">
        @Html.LabelFor(model => model.BistroReservations_TypeOfSeatingID, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("BistroReservations_TypeOfSeatingID", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.BistroReservations_TypeOfSeatingID, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.TableNoID, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("TableNoID", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.TableNoID, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.BistroReservations_StatusID, "BistroReservations_StatusID", htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("BistroReservations_StatusID",null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.BistroReservations_StatusID, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Comment, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor( model => model.Comment, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Comment, "", new { @class = "text-danger" })

            @Ajax.ActionLink("Add Furniture", "Furniture", new AjaxOptions
       {
           HttpMethod = "GET",
           UpdateTargetId = "Furniture",
           InsertionMode = InsertionMode.InsertAfter
       })

        </div>
    </div>



    <div id="Furniture" class="form-group">
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
</div>

}

ActionResult returning the furniture partial view

public PartialViewResult Furniture()
    {

        ViewBag.BistroReservations_FurnitureID = new SelectList(db.BistroReservations_Furnitures, "BistroReservations_FurnitureID", "Description");

        return PartialView("_Furniture");
    }

Furniture partial view

@model CdvPortal.Models.BistroReservations.BistroReservations_ReservationFurniture

<div class="form-horizontal">
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(model => model.BistroReservations_FurnitureID, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownList("BistroReservations_FurnitureID", null, htmlAttributes: new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.BistroReservations_FurnitureID, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group ">
        @Html.LabelFor(model => model.Quantity, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Quantity, "", new { @class = "text-danger" })
        </div>
    </div>      
</div>

Furniture Model

 public class BistroReservations_ReservationFurniture
{
    public int BistroReservations_ReservationFurnitureID { get; set; }

    public int BistroReservations_ReservationID { get; set; }       
    public virtual BistroReservations_Reservation BistroReservations_Reservation { get; set; }

    [Display(Name="Furniture Type")]
    public int BistroReservations_FurnitureID { get; set; }
    public virtual BistroReservations_Furniture BistroReservations_Furniture { get; set; }

    public int Quantity { get; set; }
}

回答1:


You @Ajax.ActionLink() method is returning views with duplicate id attributes (invalid html) and duplicate name attributes which are not prefixed with the correct property name and do not include indexers for binding to a collection. For example if the collection property is named FurnitureItems then the html needed for the Quantity property of typeof BistroReservations_ReservationFurniture would need to be

<input type="text" name="FurnitureItems[0].Quantity" ...>
<input type="text" name="FurnitureItems[1].Quantity" ...>

You can use the BeginCollectionItem helper to generate the controls, which might look like (assuming the property is named FurnitureItems)

@model CdvPortal.Models.BistroReservations.BistroReservations_ReservationFurniture
@using (Html.BeginCollectionItem("FurnitureItems"))
{
  ....
  @Html.LabelFor(m => m.Quantity, ..)
  @Html.EditorFor(m => m.Quantity, ..)
  @Html.ValidationMessageFor(m => m.Quantity, ..)
}

This will generate the correct prefix and add include an indexer based on a guid which also allows you to delete items from the collection. This article explains the usage in more detail

A pure client side alternative is shown in this answer. This gives better performance, but is harder to maintain since changing anything in the BistroReservations_ReservationFurniture model means updating the client side template.



来源:https://stackoverflow.com/questions/29875633/model-binding-doesnt-work-when-multiple-instances-of-the-same-partial-view-are

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