MVC如何使用List Items POST动作方法传递对象列表

时间:2014-01-11 15:46:48

标签: asp.net-mvc-3 razor

我想从Razor视图向控制器发布项目列表,但我得到一个对象列表为null 我的班级结构是

型号:

List<Subjects> modelItem

class Subjects
{
    int SubId{get;set;}
    string Name{get;set;}
    List<Students> StudentEntires{get;set;}
}

class StudentEntires
{
    int StudId{get;set;}
    string Name{get;set;}
    int Mark{get;set;}
}

模型本身是一个项目列表,每个项目也包含子项目列表。示例模型是主题列表,每个主题包含学生列表,我想为每个学生输入标记

我的观点就像

@model IList<Subjects>  
@{   
   Layout = "~/Views/Shared/_Layout.cshtml";
}
@using (Html.BeginForm())
{    
   @Html.ValidationSummary(true)
   if (Model.Count > 0)
   {
       @for (int item = 0; item < Model.Count(); item++)
       {
           <b>@Model[item].Name</b><br />
           @foreach (StudentEntires markItem in Model[item].StudentEntires)
           {
               @Html.TextBoxFor(modelItem => markItem.Mark)
           }
       }
       <p style="text-align:center">
           <input type="submit" class="btn btn-primary" value="Update" />
       </p>
    }
}

在控制器中

    [HttpPost]
    public ActionResult OptionalMarks(int Id,ICollection<Subjects> model)
    {
        //BUt my model is null. Any idea about this?
    }

2 个答案:

答案 0 :(得分:22)

你发现这很困难,因为你没有充分利用MVC框架的全部功能,所以请允许我提供一个有效的例子。

首先,让我们创建一个视图模型来封装视图的数据要求:

public class SubjectGradesViewModel
{
    public SubjectGradesViewModel()
    {
        Subjects = new List<Subject>();
    }

    public List<Subject> Subjects { get; set; }
}

接下来,创建一个表示主题模型的类:

public class Subject
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Student> StudentEntries { get; set; }
}

最后,一个代表学生的课程:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Grade { get; set; }
}

此时,您拥有表示数据所需的所有类。现在让我们创建两个控制器动作,包括一些示例数据,以便您可以看到它是如何工作的:

public ActionResult Index()
{
    var model = new SubjectGradesViewModel();

    // This sample data would normally be fetched
    // from your database
    var compsci = new Subject
    {
        Id = 1,
        Name = "Computer Science",
        StudentEntries = new List<Student>()
        {
            new Student { Id = 1, Name = "CompSci 1" },
            new Student { Id = 2, Name = "CompSci 2" },
        }
    };

    var maths = new Subject
    {
        Id = 2,
        Name = "Mathematics",
        StudentEntries = new List<Student>()
        {
            new Student { Id = 3, Name = "Maths 1" },
            new Student { Id = 4, Name = "Maths 2" },
        }
    };

    model.Subjects.Add(compsci);
    model.Subjects.Add(maths);

    return View(model);
}

[HttpPost]
public ActionResult Index(SubjectGradesViewModel model)
{
    if (ModelState.IsValid)
    {
        return RedirectToAction("Success");
    }

    // There were validation errors
    // so redisplay the form
    return View(model);
}

现在是构建视图的时候了,这部分在将数据发送回控制器时尤为重要。首先是Index视图:

@model SubjectGradesViewModel

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)

    @Html.EditorFor(m => m.Subjects) <br />
    <input type="submit" />
}

您会注意到我只是使用Html.EditorFor,同时传递Subjects作为参数。我这样做的原因是因为我们要创建EditorTemplate来代表Subject。我稍后会解释。现在,只需知道EditorTemplatesDisplayTemplates是MVC中的特殊文件夹名称,因此它们的名称和位置非常重要。

我们实际上要创建两个模板:一个用于Subject,另一个用于Student。为此,请按照下列步骤操作:

  1. 在视图的当前文件夹中创建EditorTemplates文件夹(例如,如果您的视图为Home\Index.cshtml,请创建文件夹Home\EditorTemplates)。
  2. 在该目录中创建一个强类型视图,其名称与您的模型匹配(即在这种情况下,您将分别创建两个视图,分别称为Subject.cshtmlStudent.cshtml(同样,命名很重要))。
  3. Subject.cshtml应如下所示:

    @model Subject
    
    <b>@Model.Name</b><br />
    
    @Html.HiddenFor(m => m.Id)
    @Html.HiddenFor(m => m.Name)
    @Html.EditorFor(m => m.StudentEntries)
    

    Student.cshtml应如下所示:

    @model Student
    
    @Html.HiddenFor(m => m.Id)
    @Html.HiddenFor(m => m.Name)
    @Html.DisplayFor(m => m.Name): @Html.EditorFor(m => m.Grade)
    <br />
    

    就是这样。如果您现在构建并运行此应用程序,在POST索引操作上设置断点,您将看到模型已正确填充。

    那么,EditorTemplates和他们的对手DisplayTemplates是什么?它们允许您创建可重复使用的视图部分,使您可以更多地组织视图。

    关于他们的好处是模板化助手,Html.EditorForHtml.DisplayFor,足够聪明,可以知道他们何时处理集合的模板。这意味着您不再需要遍历项目,每次手动调用模板。您也不必执行任何nullCount()检查,因为帮助程序将为您处理所有问题。你留下的观点很干净,没有逻辑。

    如果要将集合POST到控制器操作,

    EditorTemplates也会生成相应的名称。这使得模型绑定到列表很多,比自己生成这些名称简单得多。有时您仍然需要这样做,但这不是其中之一。

答案 1 :(得分:2)

将操作方法​​签名更改为

public ActionResult OptionalMarks(ICollection<Subjects> model)

因为在你的HTML中,它看起来不像那里有任何名为Id的东西。这不是你的主要问题。

接下来,使用foor循环执行以下操作

@for(int idx = 0; idx < Model[item].StudentEntires.Count();idx++)
{
    @Html.TextBoxFor(_ => Model[item].StudentEntries[idx])
}

可能由于foreach使用了StudentEntries循环,模型绑定器无法拼凑所有内容,因此返回NULL。

编辑:

以下是一个例子:

<强>控制器

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var viewModel = new IndexViewModel();

        var subjects = new List<Subject>();
        var subject1 = new Subject();

        subject1.Name = "History";
        subject1.StudentEntires.Add(new Student { Mark = 50 });
        subjects.Add(subject1);

        viewModel.Subjects = subjects;

        return View(viewModel);
    }

    [HttpPost]
    public ActionResult Index(IndexViewModel viewModel)
    {
        return new EmptyResult();
    }
}

查看

@model SOWorkbench.Controllers.IndexViewModel

@{
    ViewBag.Title = "Home Page";
}

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    if (Model.Subjects.Any())
    {
        int subjectsCount = Model.Subjects.Count();
        for (int item = 0; item < subjectsCount; item++)
        {
            <b>@Model.Subjects[item].Name</b><br />            
            int studentEntriesCount = Model.Subjects[item].StudentEntires.Count();

            for(int idx = 0;idx < studentEntriesCount;idx++)
            {
                @Html.TextBoxFor(_ => Model.Subjects[item].StudentEntires[idx].Mark);
            }
        }
        <p style="text-align:center">
            <input type="submit" class="btn btn-primary" value="Update" />
        </p>
    }
}

发布表单时,您应该会看到数据返回viewModel对象。