涉及不同模型时,如何将主视图和部分视图值传递给控制器​​?

How to pass main and partial views values to the controller when different models are involved?

原题: 这可能是一件简单的事情,但我已经找了 30 多个小时,但找不到适合我的答案(也尝试了很多方法)。

所以,我想要实现的是,我必须在视图上显示一些数据,并在同一视图中获取一些值。为此,我创建了一个主视图,它显示了一些数据并接受了一些值。也是主视图中的局部视图,它也显示一些数据并接受一些值。

主视图和分部视图的模型不同,相关的数据库表也不同。

主视图模型:

public class VMBooking : VMBase
    {
        public int BookingID { get; set; }
        public Guid BookingGUID { get; set; }
        public int NoOfChildrenArrived { get; set; }
       ...
        public List<VMBookingGuest> VMBookingGuests { get; set; }
       ...
    }

部分视图模型

 public class VMBookingGuest
    {
        public int BookingGuestID { get; set; }
        public int BookingID { get; set; }
        public int GuestID { get; set; }
        public string GuestName { get; set; }
        public string GuestCNIC { get; set; }
        public bool IsCNIC { get; set; }
        public bool IsGuestArrived { get; set; }
        public bool IsGuestDeparted { get; set; }
    }

现在我已经成功的在partial view中通过了partial view model,我的数据也显示出来了..

我的主视图 CSHTML:

 @using VMBooking  
<form class="form-horizontal" id="validation-form" method="post" defaultbutton="btnSubmit">
          @Html.HiddenFor(c => c.BookingID)
          @Html.HiddenFor(c => c.BookingGUID)
                           
          @Html.Partial("_Arrival", Model.VMBookingGuests)
                                   
          <div class="form-group">
          <input asp-for="@Model.NoOfChildrenArrived" type="text" id="NoOfChildrenArrived" />
          </div>    
          <button type="submit" id="btnSubmit">Submit</button>
    </form>

局部视图 CSHTML:

 @foreach (var item in Model)
            {
                <tr class="center">
                    <td>@item.GuestName</td>
                    <td>@item.GuestCNIC</td>
                    <td>
                        <div>
                            <input id="IsGuestArrived" name="IsGuestArrived" asp-for="@item.IsGuestArrived" type="checkbox"/>
                        </div>
                    </td>
                </tr>
            }

对应控制器动作如下

 [HttpGet]
        public IActionResult Arrival(int id)
        {
            VMBooking model = _BookingRepo.GetBookingByID(id);
            model.VMBookingGuests = _BookingRepo.GetGuestinfo(id);
            PopulateDropdowns(model);
            return View(model);
        }

        [HttpPost]
        public IActionResult Arrival(VMBooking vmBooking)
        {
            VMBooking model = _BookingRepo.GetBookingByID(vmBooking.BookingID);
            model.VMBookingGuests = _BookingRepo.GetGuestinfo(vmBooking.BookingID);
            if (model != null)
            {
                _BookingRepo.ArrivalUpdate(vmBooking);
                foreach (var item in model.VMBookingGuests)
                {
                    _BookingRepo.GuestArrivalUpdate(item);
                }
                return RedirectToAction("Index");
            }
            PopulateDropdowns(model);
            return View();
        }
  

一切正常,但问题出现了,我必须在主视图上的单个提交按钮上提交此组合输入数据(来自主视图和部分视图)。 当我按下提交按钮时,只有我的主视图的值被传递到控制器,而不是部分视图的值。

请注意,对于每个访客条目,我必须将复选框值列表 (id="IsGuestArrived") 传递给控制器​​。

如前所述,我尝试了多种不同的方法,但其中 none 对我有用。所以我想问,实现这一目标的合适方法是什么?

编辑: 我找到了查询的答案,现在我想显示我根据@KingKing 的建议对代码所做的更改...

我所做的是在我的主视图中插入部分标签而不是@html.partial,所以我的主视图的代码为

 @using VMBooking  
    <form class="form-horizontal" id="validation-form" method="post" defaultbutton="btnSubmit">
              @Html.HiddenFor(c => c.BookingID)
              @Html.HiddenFor(c => c.BookingGUID)
                               
              <partial name="_Arrival" for="VMBookingGuests" />
                                       
              <div class="form-group">
              <input asp-for="@Model.NoOfChildrenArrived" type="text" id="NoOfChildrenArrived" />
              </div>    
              <button type="submit" id="btnSubmit">Submit</button>
        </form>

至于我的部分观点,我同意

  @foreach (var item in Model)
                {
                <input type="hidden" asp-for="@Model[i].GuestID" />
                <input type="hidden" asp-for="@Model[i].BookingID" />

                    <tr class="center">
                        <td>@item.GuestName</td>
                        <td>@item.GuestCNIC</td>
                        <td>
                            <div>
                                <input id="IsGuestArrived" name="IsGuestArrived" asp-for="@item.IsGuestArrived" type="checkbox"/>
                            </div>
                        </td>
                    </tr>
                }

注意我在局部视图中使用的隐藏值

并且在我的控制器中而不是使用

 model.VMBookingGuests = _BookingRepo.GetGuestinfo(vmBooking.BookingID);

我用过

    model.VMBookingGuests = vmBooking.VMBookingGuests;

因为没有隐藏值,无法识别来宾 ID。

我认为您应该按如下方式更改分部视图的定义

@Html.Partial("_Arrival", Model)

然后设置局部视图代码如下,将数据正确发送到控制器。

@using VMBooking  
@foreach (var item in Model.VMBookingGuests)
{
     <tr class="center">
        <td>@item.GuestName</td>
        <td>@item.GuestCNIC</td>
        <td>
           <div>
              <input id="IsGuestArrived" name="IsGuestArrived" asp-for="@item.IsGuestArrived" type="checkbox"/>
           </div>
        </td>
      </tr>
}

如果这不起作用,请使用下面的代码

[HttpPost]
public IActionResult Arrival(VMBooking vmBooking, VMBookingGuest[] VMBookingGuests)
{
    .................................
}

EditorFor 帮助程序可能更适合您尝试做的事情,但我建议您先简化事情(然后您可以走那条路)。

所以,而不是这个(顺便说一下,它会生成一些无效的标记):

@using VMBooking  
<form class="form-horizontal" id="validation-form" method="post" defaultbutton="btnSubmit">
    @Html.HiddenFor(c => c.BookingID)
    @Html.HiddenFor(c => c.BookingGUID)
                   
    @Html.Partial("_Arrival", Model.VMBookingGuests)
                           
    <div class="form-group">
        <input asp-for="@Model.NoOfChildrenArrived" type="text" id="NoOfChildrenArrived" />
    </div>    
    <button type="submit" id="btnSubmit">Submit</button>
</form>

使用这样的东西:

@using VMBooking  
<form class="form-horizontal" id="validation-form" method="post" defaultbutton="btnSubmit">
    <input type="hidden" asp-for="BookingID" />
    <input type="hidden" asp-for="BookingGUID" />
                           
    <table>
        @for (int i = 0; i < Model.VMBookingGuests.Count; i++)
        {
            <tr class="center">
                <td>@Model.VMBookingGuests[i].GuestName</td>
                <td>@Model.VMBookingGuests[i].GuestCNIC</td>
                <td>
                    <div>
                        <input type="checkbox" asp-for="VMBookingGuests[i].IsGuestArrived" />
                        <input type="hidden" asp-for="VMBookingGuests[i].BookingGuestID" />
                    </div>
                </td>
            </tr>
        }
    </table>
                                   
    <div class="form-group">
        <input asp-for="NoOfChildrenArrived" type="text" id="NoOfChildrenArrived" />
    </div>    
    <button type="submit" id="btnSubmit">Submit</button>
</form>

您需要某种带有 VMBookingGuests[0].IsGuestArrived 等名称的标记,以便在处理 POST 操作时正确地对值进行模型绑定,并且只有具有某些属性的属性视图中的某种成功控件将被提交 - 因此添加了 BookingGuestID 属性 的隐藏输入。还有很多其他资源可以解释为什么您需要这种命名风格,但要点是,如果您有类似的东西(作为实际提交的输入的名称):

VMBookingGuests[0].IsGuestArrived
VMBookingGuests[1].IsGuestArrived
VMBookingGuests[4].IsGuestArrived
VMBookingGuests[5].IsGuestArrived

然后 modelbinder 停止从索引“中断”的点重建 VMBookingGuests 列表 - 在该示例中,当索引从 1 跳到 4 时。它也只会从某些东西构建列表像 VMBookingGuests[0].SomePropertyHere - 你不能只从每个客人那里得到一个名为 SomePropertyHere 的输入,像 SomePropertyHere[] 这样的东西也不起作用。

现在,您在 POST 操作中有这个:

foreach (var item in model.VMBookingGuests)
{
    _BookingRepo.GuestArrivalUpdate(item);
}

这似乎表明您正在将“数据”模型用作“视图”模型,并依靠您的 ORM 来确定发生了什么变化。在这种情况下,您需要为访客模型的每个 属性 添加隐藏字段。如果可以选择编写“服务”样式的方法,那么您实际上只需要 ID 属性(在您的情况下为 BookingGuestID)和 IsGuestArrived 属性(因为那是唯一 应该 改变的东西)。此类方法的示例可能是:

public bool UpdateGuestArrivals(int bookingID, params VMBookingGuest[] guests)
{
    bool success = false;

    if(guests?.Any() == true)
    {
        foreach(var guest in guests)
        {
            var bookingGuest = nameofyourDbContexthere.VMBookingGuests.SingleOrDefault(m => m.BookingID == bookingID && m.BookingGuestID == guest.BookingGuestID);
            if(bookingGuest != null)
            {
                bookingGuest.IsGuestArrived = guest.IsGuestArrived;
            }
        }
 
        nameofyourDbContexthere.SaveChanges();
        success = true;
    }

    return success;
}

那个例子中有很多假设,但我认为这个想法很清楚。您可能可以将其更改为使用您的存储库 class.

在部分视图中呈现的元素应该能够在其名称以正确的路径为前缀的情况下呈现。 Html.Partial有点棘手(最后会有解决方案)。但是如果你使用标签助手 <partial>,使用 for 属性会更容易(注意它不同于 model 属性,后者不会沿当前名称路径传递),就像这样:

<partial name="_Arrival" for="VMBookingGuests"/>

您的部分视图也需要更新,因为您正在映射一个数组,您需要使用索引来访问每个数组成员,以便可以正确呈现每个项目的名称(使用索引),像这样:

<!-- NOTE: this requires the view model of your partial view must support accessing item by index, like an IList -->
@for(var i = 0; i < Model.Count; i++) {
            <tr class="center">
                <td>@Model[i].GuestName</td>
                <td>@Model[i].GuestCNIC</td>
                <td>
                    <div>
                        <input id="IsGuestArrived-@i" asp-for="@Model[i].IsGuestArrived" type="checkbox"/>
                    </div>
                </td>
            </tr>
}

现在它应该可以正常工作,因为元素名称已正确呈现,如 VMBookingGuests[0].IsGuestArrived、...

现在 Html.Partial,你仍然可以使用它,但你需要通过 ViewDataDictionary 传入前缀信息,你需要自己构建该前缀,但这很容易,因为我们只使用标准方法来获取它,如下所示:

@{
    var vd = new ViewDataDictionary(ViewData);
    vd.TemplateInfo.Prefix = Html.NameFor(e => e.VMBookingGuests);
}

<!-- for Html.Partial -->
@Html.Partial("_Arrival", Model.VMBookingGuests, vd)

现在,通过传递前缀信息,元素名称也可以像使用 <partial> 一样正确呈现。当然,对于上面的部分视图,您仍然需要相同的更正代码。