MVC4中创建和编辑使用相同的视图

41

我们是否可以为创建和编辑操作使用单个剃刀视图?

如果是,我们该如何实现这一点?


如果我想要为模型的创建、编辑和可视化(只读)实现一个视图,怎么办?这是三个不同的操作,但始终只有一个控制器。 - carloswm85
6个回答

57

我不建议这样做。

这应该是一个相当长的答案,因为在正常的MVC GET/POST工作流程中涉及到很多内容、请求和工作流。我将尝试用最少的信息回答您的问题,并解释为什么我不建议使用相同的视图。

首先,为什么?

  1. 你无法控制视图,可能会有过度发布的问题;
  2. 没有灵活性;
  3. 不能重复使用视图或部分;
  4. 难以维护视图(对视图进行一次更改必须在两个操作上进行测试)。

我建议采用的方法是拥有不同的操作/视图,但共享通用代码:

正常创建两个视图。

你会有重复的代码,但并非所有代码都相同,例如,你可能不想在创建操作中发送ID,这与你的问题直接关联,但使用相同的视图意味着你也发送相同的数据,这是不推荐的,特别是对于过度发布或批量赋值。关于批量赋值的更多信息在这里(这里我使用的是一种架构方法)。

所以让我们从你将在控制器中接收到的内容开始。

在这种情况下,我使用了继承,但这并不是唯一的策略。

绑定模型

public class UpdateBindingModel : CreateBindingModel {
    // since we are not using the same binding model, 
    // we can have a "real" validation rules on our update binding and view.
    [Required]
    public int? Id {get;set;}
}

public class CreateBindingModel {
    // no id here prevent overposting.
    [Required]
    public string Name {get;set;}
    [Required]
    public int? CountryId {get;set;}
}

这将确保您发送到Create和Edit的数据是所需的最小数据,没有多余的数据。

然后让我们看一下将发送到View的视图模型,在此示例中,我将包括一个列表,用于选择某些值,但不应将列表本身发布到控制器,只应发布所选值。

视图模型

public class CreateViewModel : CreateBindingModel {
    public IEnumerable<SelectListItem> CountryList {get;set;}
}

public class UpdateViewModel : UpdateBindingModel {
    public IEnumerable<SelectListItem> CountryList {get;set;}
}

正如您所看到的,这样可以给您带来很大的灵活性,但仍然存在一些重复的代码(需要在视图模型中提供额外信息),可以通过几种方式来缓解(取决于需求/上下文):

  1. 有一个操作来检索公共数据,并使用 @Html.Action("GetCountryList");
  2. 使用相同的视图模型,即 CreateUpdateViewModel,并在视图中丢弃额外的 UpdateBindingModel 属性,但仍然在 POST 时发布相应的模型。
  3. 将绑定模型作为属性,并在特定视图中选择其中之一。(最好使用 @Html.EditorFor 而不是部分视图,这样模型绑定器将无需对代码进行其他更改即可工作)

控制器操作将如下所示:

Controller

[HttpGet]
public ActionResult Create(){
    ViewData.Model = new CreateViewModel();
    return View();
}

[HttpPost]
public RedirectToRouteResult Create(CreateBindingModel binding) {
    // check valid model state and create data
    return RedirectToAction("Index");
}

[HttpGet]
public ActionResult Update(int id) {
    var objectToEdit = service.GetObjectToEdit(id);
    ViewData.Model = new UpdateViewModel(objectToEdit);
    return View();
}

[HttpPost]
public RedirectToRouteResult Update(UpdateBindingModel binding) {
    // check valid model state and update data
    return RedirectToAction("Index");
}

您的观点:

观点

Update.cshtml
<form action="Update">
    @Html.HiddenFor(Model.Id);
    @Html.Partial("EditFieldsPartial")
    <button>delete</button> // no delete button on create.
    <button>create new</button> // you can have a create new instead of update.
</form>

Create.cshtml
<form action="Create">
    @Html.Partial("EditFieldsPartial")
</form>

注意: 代码不完整,并且为了简洁和清晰大多数情况下没有使用辅助程序。请勿复制粘贴 :D


你的Controller中的Update(int id)函数,不应该使用CreateBindingModel而是应该使用UpdateBindingModel来包含Id吗? - brianestey
@brianestey 发现的不错。我修正了它,但是你应该使用你的ViewModels而不是bindingmodel。请检查更新后的答案。 - Bart Calixto
Create操作中,如果ModelState无效,我们如何返回Create.cshtml视图?(它需要CreateViewModel,但在Create操作中我们只有CreateBindingModel - tchelidze
@tchelidze 返回 Create() - Bart Calixto
1
@BartCalixto 真喜欢这个答案。 - pim
显示剩余6条评论

47

当然可以。

在提交时,检查您的控制器中主键是否具有值0,如果是则插入,否则更新。

创建和编辑的视图应该是相同的。

只需记得包括:

@Html.HiddenFor(model=>model.ID)

依据您的观点

例如:

模型:

public class DescriptionModel
{
    [Key]
    public int ID { get; set; }

    public string Description { get; set; }
}
@model DescriptionModel

@using (Html.BeginForm("CreateEdit"))
{
    @Html.HiddenFor(model=> model.ID)
    @Html.EditorFor(model=> model.Description)
    <input type="submit" value='Submit' />
}

描述模型控制器:

public ActionResult Create()
{
    return View("CreateEdit", new DescriptionModel());
}
public ActionResult Edit(int id)
{
    return View("CreateEdit", db.DescriptionModels.Find(id));
}

// Submit and add or update database
[HttpPost]
public ActionResult CreateEdit(DescriptionModel model)
{
    if (ModelState.IsValid)
    {
       // No id so we add it to database
       if (model.ID <= 0)
       {
           db.DescriptionModels.Add(model);
       }
       // Has Id, therefore it's in database so we update
       else
       {
           db.Entry(model).State = EntityState.Modified;
       }
       db.SaveChanges();
       return RedirectToAction("Index");
    }

    return View(model);
}

6
我认为这种方法容易受到过度发布攻击的威胁。例如,黑客可以导航到他们记录的编辑页面并修改隐藏字段 DescriptionModel.ID 的值,将其指向另一个他们不应该访问的记录。当他们提交表单时,它将修改那个其他记录而不是他们自己的记录。Bart 的回答提供了一种解决这个安全问题的替代方案。 - Ifligus
@Ifligus 他可以创建另一个ID作为GUID,因为通过表示形式暴露数据库ID并不是一个好的实践。 - gog
1
我认为您可以只使用AddOrUpdate,这样您甚至都不需要检查,对吧? - WtFudgE
根据Julie Lerman的说法,AddOrUpdate方法旨在在迁移期间运行种子时防止重复。在生产环境中使用可能会导致意外/不希望的结果。 - techturtle
3
你可以用一个简单的 if 语句来解决安全问题,比如(伪代码):if Model.User != currentUser then throw new ForbiddenException()。我宁愿这样做,也不愿意维护两个相对相似的表单。 - Jeff

3

一个视图可以用于创建和编辑操作,并使用相同的模型。然而,我强烈建议仔细考虑。在许多情况下,您将希望为编辑操作使用不同的视图(例如,隐藏一些不应该被编辑的输入),并且模型可能略有不同,尽管它可能共享一些(或大多数)值。这些差异将导致视图中的某些条件,检查您是在创建还是编辑 - 这可能会使代码混乱。 结论:在决定是否拥有共享视图之前,请考虑编辑屏幕与创建屏幕的差异有多大,然后再做决定。


2

我不建议这种方法,但你可以将主表单从一个部分加载到两个视图中。


2

当然可以这样做,但通常我会尽量避免。如果创建和编辑操作几乎相同,那么您最终会在控制器中复制很多代码。通常在这种情况下,我会在我的“添加”控制器上只有几个字段,然后一旦项目被添加,我就会将用户重定向到编辑页面,让他们填写其余的信息。


0
[HttpGet]
 public ActionResult myFun(int id = 0)
        {
            MyClass cls = new MyClass();
            if (id == 0)
            {
                //Insert mode ... no data will be shown to textboxes , when primary key ie. id=0
               //Display whole data
            }
            else
            {
                //Update mode... if id is not 0 ,data will be shown to textboxes
            }

            return View(cls);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接