复杂的ViewModel。 View返回一个空的ViewModel。 [HttpPost]动作

时间:2014-11-27 21:36:02

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

我遇到了一个问题,即通过POST复制一个复杂的ViewModel并且它的所有对象组件都为null,即使我已经在Action中初始化它们并使用GET方法将整个ViewModel返回给View。

让我解释一下情况。我有一个View的复杂模型,它包含三个部分: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()操作时,所有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

PartialView:

@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; }
}

1 个答案:

答案 0 :(得分: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.
}