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

问题描述

原始问题: 这可能是一件简单的事情,但我已经找了 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; }
    }

现在我已经成功地通过了局部视图中的局部视图模型并且我的数据也正在显示..

我的主视图 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") 传递给每个访客条目的控制器。

正如之前所说,我尝试了多种不同的方法,但没有一种对我有用。所以我在问,实现这一目标的合适方法是什么?

编辑: 我已经找到了我的查询的答案,现在我想显示我根据 @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 无法被识别。

解决方法

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 操作时正确地将值绑定到模型,并且只有在 POST 操作中具有某种成功控制的属性视图将被提交 - 因此为 BookingGuestID 属性添加隐藏输入。还有很多其他资源可以解释为什么您需要这种命名风格,但要点是如果您有这样的东西(作为实际提交的输入的名称):

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

然后模型绑定器停止从索引“损坏”的点重建 VMBookingGuests 列表 - 在该示例中,当索引从 1 跳到 4 时。它也只会从类似 VMBookingGuests[0].SomePropertyHere 的列表构建列表{1}} - 您不能只从每个客人那里获得一个名为 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;
}

那个例子中有很多假设,但我认为这个想法很清楚。您可以更改它以使用您的 repo 类。

,

在局部视图中呈现的元素应该能够以其名称加上正确路径的前缀来呈现。 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> 一样正确呈现。当然,对于上面的部分视图,您仍然需要相同的更正代码。

,

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

@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)
{
    .................................
}