【问题标题】:Complex ViewModel. View returns an empty ViewModel. [HttpPost] Action复杂的视图模型。 View 返回一个空的 ViewModel。 [HttpPost] 行动
【发布时间】:2014-11-27 21:36:01
【问题描述】:

我对通过 POST 接收复杂 ViewModel 的操作存在问题,并且它的所有对象组件均为空,即使我已在操作中初始化它们并使用 GET 方法将整个 ViewModel 返回到视图。

让我解释一下情况。我有一个包含三个部分的视图的复杂模型:Applicant 详细信息、Application 详细信息和Recordings 的列表。这个视图很复杂,(1) 让我查看我正在为其创建应用程序的 Applicant 的详细信息,(2) 有一个我想从中选择的录音列表,然后我可以添加到 Application。这是我的 ViewModel:

public class ApplicantApplicationRecordingsViewModel
{
    // Applicant
    public Applicant Applicant { get; set; }

    // Application
    public Application Application { get; set; }
    public SelectList UsageTypeSelectList { get; private set; }
    public SelectList UsageEndAppSelectList { get; private set; }

    // Recordings
    public IEnumerable<RecordingViewModelApp>
                                RecordingsViewModelApp { get; set; }

    public ApplicantApplicationRecordingsViewModel()
        : this(new MyDBContext())
    {
    }
    public ApplicantApplicationRecordingsViewModel(MyDBContext dbContext)
    {
            PopulateUsageTypeSelectList(dbContext);
            PupulateUsageEndAppSelectList(dbContext);
    }

    private void PopulateUsageTypeSelectList(MyDBContext dbContext,
                                int? usageTypeSelected = null)
    {
        IEnumerable<UsageType> utQuery =
                dbContext.UsageTypes.OrderBy(
                ut => ut.UsageTypeName).ToList();
        this.UsageTypeSelectList =
                new SelectList(utQuery,
                    "UsageTypeID",
                    "UsageTypeName",
                    usageTypeSelected);
    }
    private void PupulateUsageEndAppSelectList(
                                MyDBContext dbContext,
                                int? usageEndAppSelected = null)
    {
        IEnumerable<UsageEndApp> ueaQuery =
                dbContext.UsageEndApps.OrderBy(uea => uea.UsageEndAppName).ToList();
        this.UsageEndAppSelectList =
                new SelectList(ueaQuery,
                    "UsageEndAppID",
                    "UsageEndAppName",
                    usageEndAppSelected);
    }
}

在控制器中,我只需为RecordingViewModelApp 填充记录列表,将申请人的详细信息放入Applicant 并将Application 对象留空以填充到视图中。

    public ActionResult Create(int? ApplicantID)
    {
        if (ApplicantID == null)
        {
            // Error 400. Bad Request Exception
        }
        ApplicantApplicationRecordingsViewModel viewModel = null;
        using (MyDBContext dbContext = new MyDBContext())
        {
            Applicant applicant =
                    dbContext.Applicants.Find(ApplicantID);
            if (applicant == null)
            {
                // Error 404. Http not found
            }
            List<RecordingViewModelApp> recordings =
                    getViewModel(
                            dbContext.Recordings.ToList(),
                            dbContext);
            viewModel =
                    new ApplicantApplicationRecordingsViewModel(dbContext);
            viewModel.Applicant = applicant;
            viewModel.RecordingsViewModelApp = recordings;
        }
        return View(viewModel);
    }

问题是当我将 ViewModel (ApplicantApplicationRecordingsViewModel) 返回到 [HttpPost] Create() Action 时,所有 View Model 的组件都是空的,例如RecordingViewModelApp 的列表为空。我错过了什么?我需要了解幕后发生的事情以及为什么默认模型绑定不起作用。

[HttpPost]
[ActionName("Create")]
public ActionResult Create_post(
                    ApplicantApplicationRecordingsViewModel viewModelToValidate)
{
    // Validation against Application only and TryToUpdate() etc.
}

干杯!


编辑:

观点

@model Project.ApplicantApplicationRecordingsViewModel

@{
    string applicantDetails = string.Format("{0} {1} {2}",
            Model.Applicant.title, Model.Applicant.firstName, Model.Applicant.lastName);
    ViewBag.Title = "Create a new application for " + applicantDetails;
}

<h2>@ViewBag.Title</h2>
<hr />
@using (Html.BeginForm())
{
    <h3>Details of the applicant</h3>
    @Html.HiddenFor(item => Model.Applicant.ApplicantID)
    @Html.HiddenFor(item => Model.Application.ApplicationID)
    <table>
        <tr>
            <th>@Html.DisplayNameFor(item => Model.Applicant.title)</th>
            <th>@Html.DisplayNameFor(item => Model.Applicant.firstName)</th>
            <th>@Html.DisplayNameFor(item => Model.Applicant.lastName)</th>
            <th>@Html.DisplayNameFor(item => Model.Applicant.telephone)</th>
            <th>@Html.DisplayNameFor(item => Model.Applicant.mobile)</th>
            <th>@Html.DisplayNameFor(item => Model.Applicant.email)</th>
        </tr>
        <tr>
            <td class="display-field">@Html.DisplayFor(item => Model.Applicant.title)</td>
            <td class="display-field">@Html.DisplayFor(item => Model.Applicant.firstName)</td>
            <td class="display-field">@Html.DisplayFor(item => Model.Applicant.lastName)</td>
            <td class="display-field">@Html.DisplayFor(item => Model.Applicant.telephone)</td>
            <td class="display-field">@Html.DisplayFor(item => Model.Applicant.mobile)</td>
            <td class="display-field">@Html.DisplayFor(item => Model.Applicant.email)</td>
        </tr>
    </table>
    <hr /> // ----------------------------------------------------------------------------------------------
    <h3>Details of the application</h3>
    <table id="main">
        <tr>
            <td>
                <table>
                    <tr>
                        <td class="editor-label first-label">@Html.DisplayNameFor(item => Model.Application.ApplicationNo)</td>
                        <td class="editor-field">
                            @Html.EditorFor(item => Model.Application.ApplicationNo) 
                            @Html.ValidationMessageFor(item => Model.Application.ApplicationNo)
                        </td>
                    </tr>
                    <tr>
                        <td class="editor-label first-label">@Html.DisplayNameFor(item => Model.Application.StartDate)</td>
                        <td class="editor-field">
                            @Html.EditorFor(item => Model.Application.StartDate) 
                            @Html.ValidationMessageFor(item => Model.Application.StartDate)
                        </td>
                    </tr>
                    <tr>
                        <td class="editor-label first-label">@Html.DisplayNameFor(item => Model.Application.EndDate)</td>
                        <td class="editor-field">
                            @Html.EditorFor(item => Model.Application.EndDate) 
                            @Html.ValidationMessageFor(item => Model.Application.EndDate)
                        </td>
                    </tr>
                    <tr>
                        <td class="editor-label first-label">@Html.DisplayNameFor(item => Model.Application.UsageTypeID)</td>
                        <td class="editor-field">
                            @Html.DropDownListFor(item => Model.Application.UsageTypeID, Model.UsageTypeSelectList, "-- Select Usage --")
                            @Html.ValidationMessageFor(item => Model.Application.UsageTypeID)
                        </td>
                    </tr>
                    <tr>
                        <td class="editor-label first-label">@Html.DisplayNameFor(item => Model.Application.UsageEndAppID)</td>
                        <td class="editor-field">
                            @Html.DropDownListFor(item => Model.Application.UsageEndAppID, Model.UsageEndAppSelectList, "-- Select Type --")
                            @Html.ValidationMessageFor(item => Model.Application.UsageEndAppID)
                        </td>
                    </tr>
                    <tr>
                        <td class="editor-label first-label">@Html.DisplayNameFor(item => Model.Application.linkToPaperVer)</td>
                        <td class="editor-field">
                            @Html.EditorFor(item => Model.Application.linkToPaperVer) 
                            @Html.ValidationMessageFor(item => Model.Application.linkToPaperVer)
                        </td>
                    </tr>
                </table>
            </td>
            <td  class="editor-label">
                @Html.DisplayNameFor(item => Model.Application.Info)
            </td>
            <td class="editor-field">
                @Html.EditorFor(item => Model.Application.Info) 
                @Html.ValidationMessageFor(item => Model.Application.Info)
            </td>
        </tr>
    </table>
    <hr /> // ----------------------------------------------------------------------------------------------
    <h3>List of recordings</h3>
    Html.RenderPartial("~/Views/Recordings/_List_App.cshtml", Model.RecordingsViewModelApp);
    <hr /> // ----------------------------------------------------------------------------------------------
    <p>
        <input type="submit" value="Create" />
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index", "Applicants")
</div>


编辑 2

部分视图:

@model IEnumerable<Project.ViewModels.RecordingViewModelApp>

@if (Model != null)
{
    <div>
        <table class="data-in-table">
            <tr>
                <th>@Html.DisplayNameFor(model => model.IsSelected)</th>
                <th>@Html.DisplayNameFor(model => model.FileLocation)</th>
                <th>@Html.DisplayNameFor(model => model.EnteredDate)</th>
                <th>@Html.DisplayNameFor(model => model.Duration)</th>
                <th>@Html.DisplayNameFor(model => model.Status)</th>
            </tr>
            @foreach (var item in Model)
            {
                <tr>
                    <td class="display-field">@Html.EditorFor(model => item.IsSelected)</td>
                    <td class="display-field">@Html.DisplayFor(model => item.FileLocation)</td>
                    <td class="display-field">@Html.DisplayFor(model => item.EnteredDate)</td>
                    <td class="display-field">@Html.DisplayFor(model => item.Duration)</td>
                    <td class="display-field">@Html.DisplayFor(model => item.Status)</td>
                </tr>
            }
        </table>
    </div>
}
else
{
    <h3>No recordings attached to this Patient</h3>
}


编辑 3

RecordingViewModelApp:

public class RecordingViewModel
{
    public int RecordingID { get; set; }
    public string FileLocation { get; set; }
    public DateTime EnteredDate { get; set; }
    public int Duration { get; set; }
    public string Status { get; set; }
}

public class RecordingViewModelApp : RecordingViewModel
{
    public bool IsSelected { get; set; }
}

【问题讨论】:

  • 你能发表你的看法吗?
  • 您是否尝试过不在视图中调用Model,而是使用item @Html.EditorFor(item => item.Application.ApplicationNo)。如果没有帮助,请添加视图的渲染 HTML 代码
  • 我现在做不到。 VS2010安装在不同的机器上,所以我必须等到明天。我只能说当模型从 GET 操作返回时正确呈现视图。当它返回 POST 时,组件为空。渲染的PartialView 是通过DisplayFor 和DisplayNameFor 等所有助手引用RecodingViewModel。我不知道这会如何影响模型本身!
  • 您需要从渲染的 HTML 中发布输入的名称,然后就会清楚为什么您的操作中有空值 :)
  • 停止使用这种不良做法! (在视图模型中使用MyDBContext,用于布局的html 表等)模型绑定正在工作,但它需要绑定值!除了ApplicantID 之外,您不会为Applicant 的任何属性呈现控件,这就是绑定的所有内容。您使用RenderPartial 进行录制并仅将模型的属性传递给它,因此控件名称与您的模型属性不匹配,因此它也将为空。您还没有显示部分视图,很难理解 我想选择并添加到应用程序 的意思

标签: c# asp.net-mvc asp.net-mvc-4


【解决方案1】:

首先修复视图模型。视图模型应该只包含简单的属性,代表您想要显示和/或编辑的内容

查看模型

public class ApplicantApplicationRecordingsViewModel
{
  public Applicant Applicant { get; set; }
  public Application Application { get; set; }
  public IEnumerable<RecordingViewModelApp> Recordings { get; set; }
  public string Title { get; set; }
  public SelectList UsageTypeSelectList { get; private set; }
  public SelectList UsageEndAppSelectList { get; private set; }
}

控制器(省略注意验证检查)

public ActionResult Create(int ApplicantID) // assume you must have a custom route for this?
{
  ApplicantApplicationRecordingsViewModel viewModel = new ApplicantApplicationRecordingsViewModel();
  Applicant applicant = dbContext.Applicants.Find(ApplicantID);
  viewModel.Applicant = applicant;
  viewModel.Title = string.Format("Create a new application for {0} {1} {2}", applicant.title, applicant.firstName, applicant.lastName);
  viewModel.Recordings = getViewModel(dbContext.Recordings.ToList(), dbContext); // not sure what this is?
  viewModel.UsageTypeSelectList = new SelectList(dbContext.UsageTypes.OrderBy(ut => ut.UsageTypeName), "UsageTypeID", "UsageTypeName");
  viewModel.UsageEndAppSelectList = new SelectList(dbContext.UsageEndApps.OrderBy(uea => uea.UsageEndAppName), "UsageEndAppID", "UsageEndAppName");  
  return View(viewModel);
}  

查看

@model Project.ApplicantApplicationRecordingsViewModel
<h2>@Model.Title</h2>
@using (Html.BeginForm())
{
  @Html.HiddenFor(item => Model.Applicant.ApplicantID) // include for post back but Application.ApplicationID not necessary (its a new application!)
  <h3>Details of the applicant</h3>
  // Add display detail for applicant, but use css for layout (position, floats etc), not tables (which are for tabular data)
  <h3>Details of the application</h3>
  // Add controls for Application but use LabelFor() so the label is associated with the control (otherwise its not a label)
  @Html.DisplayNameFor(m => m.Application.ApplicationNo)
  @Html.EditorFor(m => m.Application.ApplicationNo) 
  @Html.ValidationMessageFor(m => m.Application.ApplicationNo)
  ....
  <h3>List of recordings</h3>
  <table>
    <thead>
      .... // add table headings
    </thead>
    <tbody>
      @Html.EditorFor(m => m.Recordings) // This uses a custom editor template to display and select recordings
    </tbody>
  </table>
  <input type="submit" value="Create" />
}

EditorTemplate (/Views/Shared/EditorTemplates/RecordingViewModelApp.cshtml)

请注意,您必须使用 for 循环或自定义 EditorTemplate 来呈现集合。您使用的 foreach 循环只会呈现重复的 id(无效 html)和 name 属性,而没有正确的索引器,因此不会回发到集合中。

@model RecordingViewModelApp
<tr>
  <td class="display-field">
    @Html.CheckBoxFor(m => m.IsSelected) // required for postback
    @Html.HiddenFor(m => m.RecordingID) // required for postback
  </td>
  <td class="display-field">@Html.DisplayFor(m => m.FileLocation)</td>
  .... // other display properties
</tr>

POST 方法

[HttpPost]
public ActionResult Create(ApplicantApplicationRecordingsViewModel model)
{
  // model is now bound with the Applicant ID, all the properties of Application
  // and the collection of Recordings with their ID and IsSelected property.
}

【讨论】:

  • Stephen,在添加您的EditorTemplate 后,它会呈现记录列表,但是当我按下Submit 时,它会抛出异常“值不能为空。参数名称:值”。我尝试填写与Application 相关的所有字段,结果是一样的。我在 POST Create 控制器的第一行设置了断点,Web 应用程序甚至没有到达这个断点。
  • 在 PartialView 中,我已将 forloop 更改为 for,添加了 @Html.HiddenFor(m =&gt; Model.ElementAt(index).RecordingID) 和 @Html.CheckBoxFor(m =&gt; Model.ElementAt(index).IsSelected) 但是 RecordingVIewModelApp 为空。
  • @ZikO,您不能使用 PartialView,只能使用 EditorTemplate(删除部分视图)。至于错误Value cannot be null. Parameter name: value,我什至看不到你在哪里有一个名为value 的属性,所以它没有意义。如果它没有到达 POST 方法的第一行,它是如何抛出异常的?
  • 我认识斯蒂芬。当我添加并应用您提供的 EditorTemplate 时发生异常。我在代码中没有类似Parameter: value 的东西,我正试图找出它可能在哪里。我想我最好关闭这个话题。在此感谢您的帮助。
  • 只要确保你使用了EditorTemplate 并且你的名字正确(以匹配类名)。
猜你喜欢
  • 2020-06-18
  • 1970-01-01
  • 1970-01-01
  • 2015-10-03
  • 1970-01-01
  • 2017-06-08
  • 1970-01-01
  • 2015-11-27
  • 1970-01-01
相关资源
最近更新 更多