【问题标题】:Binding list of nested viewmodels on post ASP.NET MVCASP.NET MVC 后嵌套视图模型的绑定列表
【发布时间】:2016-09-06 12:03:20
【问题描述】:

我有以下视图模型结构: ItemViewModel 包含类型为 BaseViewModel 的属性 Model。

在这个具体示例中,Model 的类型是 WeekManagerWorkScheduleViewModel。该视图模型依次由 ManagerWorkScheduleViewModel 类型的 IEnumerable 组成。

在 ItemView.cshtml 中使用了以下内容:

<form class="form-horizontal" id="@Model.FormName" name="@Model.FormName" onsubmit="@Model.OnSubmitFunction">
    <div class="form-group form-group-sm">
        @Html.EditorFor(model => model.Model, Model.ItemViewName + "View")
    </div>
    <button class="btn pull-left" type="submit" style="margin: 2px;">
      <span class="glyphicon glyphicon-save"></span>
        @Model.ViewConfiguration.SaveText @Model.ItemTitle
     </button>
 </form>

我省略了一些细节(基本上是一堆 if 确定是否添加 CRUD 按钮。Model.ItemViewName 是类型名(在本例中为 WeekManagerWorkScheduleViewModel)。

public class WeekManagerWorkScheduleViewModel : BaseViewModel
{ 
    [HiddenInput]
    public int RegionId { get; set; }
    [HiddenInput]
    public IEnumerable<DateTime> Dates { get; set; }

    public IEnumerable<ManagerWorkScheduleViewModel> WorkSchedules { get; set; } 
}

WeekManagerWorkScheduleView.cshtml 如下所示:

@using DRSTransportPortal.ViewModels
@model WeekManagerWorkScheduleViewModel
@{
   ViewBag.Title = "Ugentlig arbejdsplan - ledere";
}

@Html.HiddenFor(m => m.RegionId)

<table class="table">
    <thead>
        <tr>
           <th><b>Leder</b></th>
           <th><b>Uge</b></th>
           <th><b>Mandag</b></th>
           <th><b>Tirsdag</b></th>
           <th><b>Onsdag</b></th>
           <th><b>Torsdag</b></th>
           <th><b>Fredag</b></th>
           <th><b>Lørdag</b></th>
           <th><b>Søndag</b></th>
       </tr>
       <tr>
           <th></th>
           <th></th>
              @foreach (var date in Model.Dates)
              {
                  <th><i><small>@date.Date.ToString("dd-MM-yyyy")</small></i></th>
              }
       </tr>
  </thead>
  <tbody>
     @Html.EditorFor(m => m.WorkSchedules)
  </tbody>
</table>

我在 Views/imalazysod/EditorTemplates 中有一个名为 ManagerWorkScheduleViewModel.cshtml 的视图(MVC 知道这个位置,因为我使用的是从 Razor 派生的自定义视图引擎):

@using DRSTransportPortal.ViewModels
@model ManagerWorkScheduleViewModel
<tr id="@Model.Id">
    <td>@Html.HiddenFor(m => m.Id)</td>
    <td>@Html.DisplayFor(m => m.ManagerName, new { htmlAttributes = new { @class = "form-control editoritem" } })</td>
    <td>@Html.DisplayFor(m => m.DateWeekText, new { htmlAttributes = new { @class = "form-control editoritem" } })</td>
     <td>@Html.EditorFor(m => m.MondayCodeChoice, new { @class = "form-control editoritem" })</td>
     <td>@Html.EditorFor(m => m.TuesdayCodeChoice, new { @class = "form-control editoritem" })</td>
     <td>@Html.EditorFor(m => m.WednesdayCodeChoice, new { @class = "form-control editoritem" })</td>
     <td>@Html.EditorFor(m => m.ThursdayCodeChoice, new { @class = "form-control editoritem" })</td>
     <td>@Html.EditorFor(m => m.FridayCodeChoice, new { @class = "form-control editoritem" })</td>
     <td>@Html.EditorFor(m => m.SaturdayCodeChoice, new { @class = "form-control editoritem" })</td>
     <td>@Html.EditorFor(m => m.SundayCodeChoice, new { @class = "form-control editoritem" })</td>
 </tr>

“Choice”属性都是使用 ChoiceViewModel.cshtml 的 ChoiceViewModel 类型。

现在,一切正常: Screenshot (names omitted). Red box indicates 1 (one) nested viewmodel

生成的 HTML 如下所示(此处仅显示第一行和前几个单元格):

<tr id="134">
    <td><input name="Model.WorkSchedules[0].Id" id="Model_WorkSchedules_0__Id" type="hidden" value="134" data-val-required="Feltet Id skal udfyldes." data-val="true" data-val-number="The field Id must be a number."></td>
    <td>OMITTED</td>
    <td>2016 - 36</td>
    <td>
    <select name="Model.WorkSchedules[0].MondayCodeChoice.SelectedValue" class="form-control editoritem dropdown" id="SelectChoices" onchange="">
       <option value="1">06:00 - 14:00</option>
       <option value="19">06:00 - 18:00</option>
       <option value="31">08:00 - 16:00</option>
       <option value="2">10:00 - 18:00</option>
       <option value="32">10:00 - 18:00</option>
       <option value="23">Bagvagt</option>
       <option value="22">Ferie</option>
       <option value="8">Fri</option>
       <option value="3">Kontor</option>
       <option value="15">Kussus</option>
       <option value="16">Syg</option>
     </select>
   </td>  
REST OF HTML IS OMMITTED (CONTINUES FOR 12 ROWS WITH 10 CELLS EACH)
</tr>

但是,当我回帖(使用 jQuery、ajax 顺便说一句)时,我得到的是: Controller breakpoint, after modelbinding

我尝试在 BaseViewModel 上放置一个自定义模型绑定器,并进行调试,但没有发现任何看起来不正常的东西。它解析正确的类型(WeekManagerWorkScheduleViewModel 等)。 使用它,我的来自 ControllerContext->...->Request 的 Forms 元素是: {Model.Id=141&ModelType=DRSTransportPortal.ViewModels.WeekManagerWorkScheduleViewModel%2c+DRSTransportPortal%2c+Version%3d1.0.0.0%2c+Culture%3dneutral%2c+PublicKeyToken%3dnull&Model.RegionId=1&Model.WorkSchedules%5b0%5d.Id =134&Model.WorkSchedules%5b0%5d.MondayCodeChoice.SelectedValue=22&Model.WorkSchedules%5b0%5d.TuesdayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.WednesdayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.ThursdayCodeChoice.SelectedValue=1&Model .WorkSchedules%5b0%5d.FridayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.SaturdayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.SundayCodeChoice.SelectedValue=1&.....所有12个嵌套视图模型继续}

我已确定以下内容属实: 所有绑定都对属性进行(默认即 {get;set;}) 包括 BaseViewModel 在内的所有视图模型都没有构造函数(即应创建默认构造函数) 所有属性和类都是公开的

所以...我的问题是,为什么模型绑定器无法创建列表的视图模型?我在其他地方使用模板没有问题,编辑 pr。人,即一次一行)。 是的,我知道有很多与 MVC 模型绑定相关的问题和答案,但似乎没有一个完全属于这一类(书籍也是如此)。真正让我感到困惑的是,它认识到需要一个包含 12 个项目的列表,但它只是没有填充。

编辑:

public class BaseViewModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
        var type = Type.GetType((string) typeValue.ConvertTo(typeof (string)), true);
        if(!typeof(BaseViewModel).IsAssignableFrom(type))
            throw new InvalidOperationException("NOT A BASEVIEWMODEL");

        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}

[ModelBinder(typeof(BaseViewModelBinder))]
public class BaseViewModel
{
    [HiddenInput]
    public int Id { get; set; }
    [HiddenInput]
    public DateTime Created { get; set; }
    [HiddenInput]
    public DateTime Edited { get; set; }
    public virtual string SelectionName { get; set; }
}

编辑 2:

    [HttpPost]
    public override async Task<JsonResult> Save(ItemViewModel item, string parentName, int? parentId)
    {
        if (item?.Model == null)
        {
            const int result = 0;
            return Json(new { result });
        }
        var model = item.Model as WeekManagerWorkScheduleViewModel;
        if (model == null)
        {
            const int result = 0;
            return Json(new { result });
        }

        foreach (var inner in model.WorkSchedules)
        {

        }
        return await base.Save(item, parentName, parentId);
    }

public class ManagerWorkScheduleViewModel : BaseViewModel
{
    [HiddenInput]
    public int? ManagerId { get; set; }
    [DisplayName("Leder")]
    public string ManagerName { get; set; }

    [HiddenInput]
    public int? DateWeekId { get; set; }
    [DisplayName("År / Uge")]
    public string DateWeekText { get; set; }

    [HiddenInput]
    public int? MondayCodeId { get; set; }

    [UIHint("ChoiceViewModel")]
    [DisplayName("Mandag")]
    public ChoiceViewModel MondayCodeChoice { get; set; }

    [HiddenInput]
    public int? TuesdayCodeId { get; set; }

    [UIHint("ChoiceViewModel")]
    [DisplayName("Tirsdag")]
    public ChoiceViewModel TuesdayCodeChoice { get; set; }

    [HiddenInput]
    public int? WednesdayCodeId { get; set; }

    [UIHint("ChoiceViewModel")]
    [DisplayName("Onsdag")]
    public ChoiceViewModel WednesdayCodeChoice { get; set; }


    [HiddenInput]
    public int? ThursdayCodeId { get; set; }

    [UIHint("ChoiceViewModel")]
    [DisplayName("Torsdag")]
    public ChoiceViewModel ThursdayCodeChoice { get; set; }

    [HiddenInput]
    public int? FridayCodeId { get; set; }

    [UIHint("ChoiceViewModel")]
    [DisplayName("Fredag")]
    public ChoiceViewModel FridayCodeChoice { get; set; }

    [HiddenInput]
    public int? SaturdayCodeId { get; set; }

    [UIHint("ChoiceViewModel")]
    [DisplayName("Lørdag")]
    public ChoiceViewModel SaturdayCodeChoice { get; set; }

    [HiddenInput]
    public int? SundayCodeId { get; set; }

    [UIHint("ChoiceViewModel")]
    [DisplayName("Søndag")]
    public ChoiceViewModel SundayCodeChoice { get; set; }
}

【问题讨论】:

  • 哦,顺便说一句,使用 ASP.NET MVC 5(我认为 ..Core 之前的那个 :))
  • 您不能使用派生类。您还没有显示相关代码(您的模型或控制器方法,但如果属性ModelBaseViewModel 的类型,那么DefaultModelBinder 将初始化BaseViewModel,而不是WeekManagerWorkScheduleViewModelWeekManagerWorkScheduleViewModel 的任何属性不在BaseViewModel 中的将被忽略
  • 如果我在模型绑定器中放置一个断点,它确实使用了正确的模型,而且,顶层模型上的所有属性(具有隐藏输入)都正确创建并给出了正确的值。此外,对于从 BaseViewModel 派生的所有视图模型,它就像一个魅力,这些视图模型没有帖子中需要的嵌套视图模型。

标签: asp.net asp.net-mvc model-binding asp.net-mvc-viewmodel


【解决方案1】:

啊,愚蠢的我。 我忘了我最初是如何制作这个框架的。

我创建这个主要是为了CRUD操作,所以我只需要为每个业务实体定义viewmodel(派生自BaseViewModel)、view、repository和dto类型以及一个空控制器。然后其他一切都使用一组标准控制器(三个派生级别,取决于所需的功能),我需要做的就是为业务实体创建一个配置条目,定义 vm、视图、repo 类型以及一些 CRUD按钮和标签的选项。 所有顶级视图模型和视图都是相同的,如 ItemView、ItemViewModel、ItemListView、SingleSelector、MultipleSelector 等。

我忘记了我一次只限于一个业务实体,因此输出了类(参见模型绑定器),所以我可以使用派生的视图模型......这很好用,除非在这种情况下很明显。

发生的情况是,当绑定尝试填充列表时,它会不断尝试使用第一个视图模型类型(因为这是 POST 之后唯一已知的类型)而不是派生的。该死!所以回到绘图表(没有嵌套:()

【讨论】:

  • 通过添加自定义模型绑定器的静态字典来解决,该字典用于代替默认库模型绑定器(问题中的模型绑定器)。所以现在,在我确实需要嵌套视图模型的少数地方,我可以。不会用任何依赖项污染我的共享库。 :)
猜你喜欢
  • 2014-07-10
  • 1970-01-01
  • 2016-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多