我们是否可以为创建和编辑操作使用单个剃刀视图?
如果是,我们该如何实现这一点?
我们是否可以为创建和编辑操作使用单个剃刀视图?
如果是,我们该如何实现这一点?
我不建议这样做。
这应该是一个相当长的答案,因为在正常的MVC GET/POST工作流程中涉及到很多内容、请求和工作流。我将尝试用最少的信息回答您的问题,并解释为什么我不建议使用相同的视图。
首先,为什么?
我建议采用的方法是拥有不同的操作/视图,但共享通用代码:
正常创建两个视图。
你会有重复的代码,但并非所有代码都相同,例如,你可能不想在创建操作中发送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;}
}
正如您所看到的,这样可以给您带来很大的灵活性,但仍然存在一些重复的代码(需要在视图模型中提供额外信息),可以通过几种方式来缓解(取决于需求/上下文):
@Html.Action("GetCountryList");
CreateUpdateViewModel
,并在视图中丢弃额外的 UpdateBindingModel
属性,但仍然在 POST 时发布相应的模型。@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
Update(int id)
函数,不应该使用CreateBindingModel而是应该使用UpdateBindingModel来包含Id吗? - brianesteyCreate
操作中,如果ModelState
无效,我们如何返回Create.cshtml
视图?(它需要CreateViewModel
,但在Create
操作中我们只有CreateBindingModel
) - tchelidze当然可以。
在提交时,检查您的控制器中主键是否具有值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);
}
DescriptionModel.ID
的值,将其指向另一个他们不应该访问的记录。当他们提交表单时,它将修改那个其他记录而不是他们自己的记录。Bart 的回答提供了一种解决这个安全问题的替代方案。 - Ifligusif
语句来解决安全问题,比如(伪代码):if Model.User != currentUser then throw new ForbiddenException()
。我宁愿这样做,也不愿意维护两个相对相似的表单。 - Jeff一个视图可以用于创建和编辑操作,并使用相同的模型。然而,我强烈建议仔细考虑。在许多情况下,您将希望为编辑操作使用不同的视图(例如,隐藏一些不应该被编辑的输入),并且模型可能略有不同,尽管它可能共享一些(或大多数)值。这些差异将导致视图中的某些条件,检查您是在创建还是编辑 - 这可能会使代码混乱。 结论:在决定是否拥有共享视图之前,请考虑编辑屏幕与创建屏幕的差异有多大,然后再做决定。
我不建议这种方法,但你可以将主表单从一个部分加载到两个视图中。
当然可以这样做,但通常我会尽量避免。如果创建和编辑操作几乎相同,那么您最终会在控制器中复制很多代码。通常在这种情况下,我会在我的“添加”控制器上只有几个字段,然后一旦项目被添加,我就会将用户重定向到编辑页面,让他们填写其余的信息。
[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);
}