如何在asp.net mvc中为复杂对象构建创建视图?

时间:2017-05-04 16:26:10

标签: c# asp.net-mvc

我正在学习asp.net mvc,使用2017年的视觉工作室社区,作为一种教学项目,我正在制作一个跟踪锻炼工作的网络应用程序。我的模型由WorkOut对象组成,这些对象具有一个练习列表(或更具体的ICollection),每个练习都有一个ICollection。这是我的模型类的基础知识。

public class WorkOut
    {
        public int Id { get; set; }
        public int Length { get; set; }
        public DateTime Date { get; set; }
        public virtual ICollection<Exercise> ExerciseList { get; set; }
    }
public class Exercise
    {

        public int Id { get; set; }
        public string Name { get; set; }
        public int WorkOutId { get; set; }
        public virtual WorkOut WorkOut { get; set; }
        public virtual ICollection<RepUnit> Sets { get; set; }
    }
public class RepUnit
    {
        public int Id { get; set; }
        public int Rep { get; set; }
        public int? Weight { get; set; }
        public int ExerciseId { get; set; }
        public virtual Exercise Exercise { get; set; }
    }

使用WorkOut作为模型自动生成视图会导致创建仅生成“长度”和“日期”属性的操作和视图。通常,自动生成的视图和控制器仅添加非虚拟属性。所以我想也许我必须做一个多步创建过程;创建锻炼,创建锻炼并添加代表,将锻炼添加到锻炼中,停止或添加其他锻炼。所以我想我让VS为我做了一些工作,我为每个模型对象输入器(WorkOutsController,ExercisesController,RepUnitsController)制作控制器和视图,然后我会删除不需要的视图甚至重构动作我实际上使用到一个新的控制器。 所以WorkOutsController我的POST动作就是这个。

public ActionResult Create([Bind(Include = "Id,Length,Date")] WorkOut workOut)
        {
            if (ModelState.IsValid)
            {
                db.WorkOuts.Add(workOut);
                db.SaveChanges();

                return RedirectToAction("Create","Exercises",new { workoutId = workOut.Id });
            }
            return View(workOut);
        }

所以我将exerciseId带到了练习控制器,但这是我不确定如何继续的地方。我想继续携带workoutId和下一步,我给练习一个名字,也显示刚刚添加的相关日期。我唯一能想到的就是在ExerciseController的GET动作中实例化一个练习,就像这样。

public ActionResult Create(int workoutID)
        {
            Exercise ex= new Exercise();
            ex.WorkOutId=workoutID;
            ex.WorkOut=db.WorkOuts(workoutID);

            return View(ex);
        }

这看起来很糟糕,我在任何例子中都没有看到过这样的事情,但似乎有效。相同的运动对象将返回到我的POST创建操作

public ActionResult Create([Bind(Include = "Id,Name,WorkOutId")] Exercise exercise)
        {
            if (ModelState.IsValid)
            {
                db.Exercises.Add(exercise);
                db.SaveChanges();
                return RedirectToAction("Create", "RepUnits", new { exerciseId = exercise.Id });
            }
            return View(exercise);
        }

如您所见,调用RepUnits控制器和关联的Create操作。我做了一些非常相似的事情;创建一个rep对象并将其传递给视图,基本上我添加reps直到我完成。最终我将创建导航以返回添加新练习或返回索引视图。

总而言之,传递整个对象似乎很浪费,也许我的整个实现都是错误的,我应该尝试以某种方式在一个表单上完成所有操作。到目前为止,谷歌搜索并没有找到我很多,因为我不确定要问什么问题,但是这篇文章Creation of objects using form data within an ASP.NET MVC application刚出现在类似的问题对话框中,而且有问题的应用程序巧合地非常相似!然而,当OP提到通过trainingId时,这是如何实现的?我想也许可以使用ViewBag,但如何让视图处理这个Id? 我曾尝试过,作为一个例子

 public ActionResult Create(int workoutId)
        {
            ViewBag.WoID = workoutId;
            return View();
        }

在ExercisesController中,然后在相关的Create视图中:

 @Html.Hidden("WorkOutId", new { ViewBag.WoID })

但是在后来的视图中,当我尝试引用锻炼日期时,它会出现空白

<div class="form-group">
            @Html.LabelFor(model => model.WorkOut.Date, "Work Out On:", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DisplayFor(model=>model.WorkOut.Date)
            </div>
        </div>

我应该在视图中做这样的事情:     @ Model.WorkOutId = ViewBag.WoID;

由于某种原因不起作用(编译器错误消息:CS1525:无效的表达式术语'='),但这是我如何传递这些ID?

1 个答案:

答案 0 :(得分:0)

支架式视图有意简化。处理相关项目需要多个注意事项,Visual Studio不会为您制作这些注意事项。但是,您可以并且非常鼓励根据您的特定需求更改脚手架视图。

例如,要在与锻炼相同的视图中创建锻炼,您只需要为Exercise生成字段,其名称将允许模型绑定器绑定已发布的数据。对于类似于CollectionProperty[N].Property的集合属性,其中N是索引。

例如,您可以通过三个练习初始化锻炼:

var model = new Workout
{
    ExerciseList = new List<Exercise>
    {
        new Exercise(),
        new Exercise(),
        new Exercise()
    }
};

然后,在您看来:

@for (var i = 0; i < Model.Exercises.Count(); i++)
{
    @Html.EditorFor(m => m.ExerciseList[i].Name)
}

但是,这里有一点需要注意:ICollection不可索引。因此,要这样做,您需要一个类型为List<Exercise>的属性。这是视图模型非常方便的地方。不过,有一种解决方法:您可以在EditorFor集合上使用Excercises。例如,上面的代码将简化为:

@Html.EditorFor(m => m.ExerciseList)

EditorFor是一个“模板化助手”,这意味着它只是使用模板来渲染传递给它的内容。值得庆幸的是,它有一些默认值,所以在某种程度上,您不必担心这一点,但它会成为问题。例如,在这里,Razor将简单地遍历ExerciseList中的项目并为每个项目呈现Exercise的模板。由于Exercise是自定义类型且没有默认模板,因此它会自省内核并为Exercise上的每个公共属性呈现模板。有时这很好用。例如,Name将使用文本框呈现,因为它应该是。不过,您还会获得IdWorkoutId的文本框,您甚至不希望在表单上看到这些文本框。

您可以通过为Exercise添加视图,为Views\Shared\EditorTemplates\Exercise.cshtml创建自己的编辑器模板来解决此问题。此视图将具有Exercise的模型,然后,只需包含您的name属性的文本框。然后,当您在EditorFor上使用ExerciseList时,它将使用该视图呈现每个Exercise

尽管如此,你可能已经意识到这仍然有些限制:你必须用一定数量的练习进行初始化,然后就是你得到的全部。这就是JavaScript的用武之地。您可以根据需要动态添加一个新的运动字段块(或删除现有的块),而不是迭代预定义的练习列表。但是,手动为此任务编写JavaScript将非常费力和密集。在这一点上,你最好使用像Knockout.js,Angular或类似的东西。除其他外,这些库为您提供了双向数据绑定,因此您可以简单地为一块运动字段设置一个JavaScript模板,然后将其绑定到JavaScript对象的ExceriseList成员(您的客户端“视图模型”)。然后,您可以通过在此JS数组中添加或删除项来简单地重复这些字段。显然,还有更多内容,但这是基本框架。您需要查阅您所使用的库的各个文档,以确定如何设置所有内容。

然后,您可以冲洗并重复所有这些以获得其他级别的关系。换句话说,完全可以通过多个练习发布锻炼的整个对象图,每个练习都有多个代表单元等,所有这一切都在一个视图中。