Asp.net mvc 成功的模型编辑,没有一堆隐藏字段
简而言之:如何成功编辑DB条目,而不需要在编辑视图中包含模型的每个字段 更新Asp.net mvc 成功的模型编辑,没有一堆隐藏字段,asp.net-mvc,viewmodel,model-binding,valueinjecter,Asp.net Mvc,Viewmodel,Model Binding,Valueinjecter,简而言之:如何成功编辑DB条目,而不需要在编辑视图中包含模型的每个字段 更新 所以我在DB中有一个条目(一篇文章)。我想编辑一篇文章。我编辑的文章有许多属性(Id、CreatedBy、DateCreated、Title、Body)。其中一些属性永远不需要更改(如Id、CreatedBy、DateCreated)。因此,在我的编辑视图中,我只希望输入可以更改的字段(如标题、正文)。当我实现这样的编辑视图时,模型绑定失败。我没有为其提供输入的任何字段都会设置为某些“默认”值(例如DateCreate
所以我在DB中有一个条目(一篇文章)。我想编辑一篇文章。我编辑的文章有许多属性(Id、CreatedBy、DateCreated、Title、Body)。其中一些属性永远不需要更改(如Id、CreatedBy、DateCreated)。因此,在我的编辑视图中,我只希望输入可以更改的字段(如标题、正文)。当我实现这样的编辑视图时,模型绑定失败。我没有为其提供输入的任何字段都会设置为某些“默认”值(例如DateCreated设置为01/01/0001 12:00:00am)。如果我为每个领域提供输入,一切都会正常进行,文章也会按预期进行编辑。我不知道说“模型绑定必然失败”是否正确,甚至“如果在编辑视图中没有为字段提供输入字段,那么系统会用不正确的数据填充字段。” 如何创建编辑视图,使我只需要为可以/需要编辑的字段提供输入字段,以便在调用控制器中的编辑方法时,正确填充DateCreated等字段,而不将其设置为某些默认的、不正确的值?以下是我目前的编辑方法:
[HttpPost]
public ActionResult Edit(Article article)
{
// Get a list of categories for dropdownlist
ViewBag.Categories = GetDropDownList();
if (article.CreatedBy == (string)CurrentSession.SamAccountName || (bool)CurrentSession.IsAdmin)
{
if (ModelState.IsValid)
{
article.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName;
article.LastUpdated = DateTime.Now;
article.Body = Sanitizer.GetSafeHtmlFragment(article.Body);
_db.Entry(article).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(article);
}
// User not allowed to edit
return RedirectToAction("Index", "Home");
}
以及编辑视图(如果有帮助):
. . .
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Article</legend>
<p>
<input type="submit" value="Save" /> | @Html.ActionLink("Back to List", "Index")
</p>
@Html.Action("Details", "Article", new { id = Model.Id })
@Html.HiddenFor(model => model.CreatedBy)
@Html.HiddenFor(model => model.DateCreated)
<div class="editor-field">
<span>
@Html.LabelFor(model => model.Type)
@Html.DropDownListFor(model => model.Type, (SelectList)ViewBag.Categories)
@Html.ValidationMessageFor(model => model.Type)
</span>
<span>
@Html.LabelFor(model => model.Active)
@Html.CheckBoxFor(model => model.Active)
@Html.ValidationMessageFor(model => model.Active)
</span>
<span>
@Html.LabelFor(model => model.Stickied)
@Html.CheckBoxFor(model => model.Stickied)
@Html.ValidationMessageFor(model => model.Stickied)
</span>
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Body)
</div>
<div class="editor-field">
@* We set the id of the TextArea to 'CKeditor' for the CKeditor script to change the TextArea into a WYSIWYG editor. *@
@Html.TextAreaFor(model => model.Body, new { id = "CKeditor", @class = "text-editor" })
@Html.ValidationMessageFor(model => model.Body)
</div>
</fieldset>
. . .
当调用Edit方法时,它们被设置为默认值。CreatedBy设置为Null,Created设置为01/01/0001 12:00:00am
为什么它们没有设置为当前在数据库中设置的值?查看模型示例:
public class ArticleViewModel {
[Required]
public string Title { get; set; }
public string Content { get; set; }
}
绑定示例
public ActionResult Edit(int id, ArticleViewModel article) {
var existingArticle = db.Articles.Where(a => a.Id == id).First();
existingArticle.Title = article.Title;
existingArticle.Content = article.Content;
db.SaveChanges();
}
这是一个简单的示例,但您应该查看ModelState以检查模型是否没有错误,检查授权并将此代码从控制器移到服务类,但是
这是另一个教训
这是正确的编辑方法:
[HttpPost]
public ActionResult Edit(Article article)
{
// Get a list of categories for dropdownlist
ViewBag.Categories = GetDropDownList();
if (article.CreatedBy == (string)CurrentSession.SamAccountName || (bool)CurrentSession.IsAdmin)
{
if (ModelState.IsValid)
{
var existingArticle = _db.Articles.First(a => a.Id = article.Id);
existingArticle.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName;
existingArticle.LastUpdated = DateTime.Now;
existingArticle.Body = Sanitizer.GetSafeHtmlFragment(article.Body);
existingArticle.Stickied = article.Stickied;
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(article);
}
// User not allowed to edit
return RedirectToAction("Index", "Home");
}
使用视图模型 通过我不断的研究来找到解决这个问题的方法,我相信使用这些被称为“视图模型”的东西是可行的。正如Jimmy Bogard在一篇文章中所解释的,ViewModels是一种“显示单个实体的信息片段”的方法
让我走上了正确的道路;我仍在查阅作者发布的一些外部资源,以便进一步掌握ViewModel的概念(Jimmy的博客文章就是其中之一)。在进一步研究之后,我发现了一些有助于ViewModel过程的工具-一个是AutoMapper,另一个是InjectValue。我之所以选择InjectValue,主要是因为它不仅可以“展平”对象(映射对象a->b),还可以“取消展平”对象(映射对象b->a)-AutoMapper很遗憾没有现成的东西-为了更新DB中的值,我需要这样做 现在,我没有将我的文章模型及其所有属性发送到我的视图,而是创建了一个只包含以下属性的ArticleViewModel:
public class ArticleViewModel
{
public int Id { get; set; }
[MaxLength(15)]
public string Type { get; set; }
public bool Active { get; set; }
public bool Stickied { get; set; }
[Required]
[MaxLength(200)]
public string Title { get; set; }
[Required]
[AllowHtml]
public string Body { get; set; }
}
创建文章时,我不发送文章对象(每个属性),而是向视图发送“更简单”的模型-我的ArticleViewModel:
//
// GET: /Article/Create
public ActionResult Create()
{
return View(new ArticleViewModel());
}
对于POST方法,我们使用发送到视图的ViewModel,并使用其数据在DB中创建一篇新文章。我们通过将ViewModel“取消平台化”到Article对象上来实现这一点:
//
// POST: /Article/Create
public ActionResult Create(ArticleViewModel articleViewModel)
{
Article article = new Article(); // Create new Article object
article.InjectFrom(articleViewModel); // unflatten data from ViewModel into article
// Fill in the missing pieces
article.CreatedBy = CurrentSession.SamAccountName; // Get current logged-in user
article.DateCreated = DateTime.Now;
if (ModelState.IsValid)
{
_db.Articles.Add(article);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
ViewBag.Categories = GetDropDownList();
return View(articleViewModel);
}
填写的“缺少的部分”是我不想在视图中设置的文章属性,也不需要在编辑视图中更新它们(或者根本不需要更新)
Edit方法基本相同,只是我们不向视图发送新的ViewModel,而是发送一个用数据库中的数据预填充的ViewModel。我们通过从数据库检索文章并将数据展平到ViewModel上来实现这一点。首先,GET方法:
//
// GET: /Article/Edit/5
public ActionResult Edit(int id)
{
var article = _db.Articles.Single(r => r.Id == id); // Retrieve the Article to edit
ArticleViewModel viewModel = new ArticleViewModel(); // Create new ArticleViewModel to send to the view
viewModel.InjectFrom(article); // Inject ArticleViewModel with data from DB for the Article to be edited.
return View(viewModel);
}
对于POST方法,我们希望获取从视图发送的数据,并用它更新存储在DB中的文章。要做到这一点,我们只需通过将ViewModel“取消平台化”到Article对象上来反转展平过程,就像我们在创建方法的后期版本中所做的那样:
//
// POST: /Article/Edit/5
[HttpPost]
public ActionResult Edit(ArticleViewModel viewModel)
{
var article = _db.Articles.Single(r => r.Id == viewModel.Id); // Grab the Article from the DB to update
article.InjectFrom(viewModel); // Inject updated values from the viewModel into the Article stored in the DB
// Fill in missing pieces
article.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName;
article.LastUpdated = DateTime.Now;
if (ModelState.IsValid)
{
_db.Entry(article).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(viewModel); // Something went wrong
}
我们还需要更改强类型的“创建和编辑”视图,以期望使用ArticleViewModel而不是Article:
@model ProjectName.ViewModels.ArticleViewModel
就这样
总之,您可以实现ViewModels,将模型的一部分传递给视图。然后,您可以只更新这些部分,将ViewModel传递回控制器,并使用ViewModel中更新的信息来更新实际模型。没有ViewModel的另一个好方法
// POST: /Article/Edit/5
[HttpPost]
public ActionResult Edit(Article article0)
{
var article = _db.Articles.Single(r => r.Id == viewModel.Id); // Grab the Article from the DB to update
article.Stickied = article0.Stickied;
// Fill in missing pieces
article.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName;
article.LastUpdated = DateTime.Now;
if (ModelState.IsValid)
{
_db.Entry(article0).State = EntityState.Unchanged;
_db.Entry(article).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(article0); // Something went wrong
}
除了答案之外,AutoMapper还可以用于对其进行解冻。
因此,对于ViewModel,您仍然需要(在视图中)为每个字段提供输入,以便模型绑定器(至少是默认的)成功绑定到模型,对吗?在您的示例中,我需要为“标题”和“内容”字段提供输入。在这种情况下,我会发现简单地做我一直在做的事情更容易——在视图中为非更改字段(Id、DateCreated等)提供隐藏字段。这样我就不用为我的每一个模型创建视图模型了。@cbergman:这就是MVC的播放方式。隐藏字段可以很容易地操作和更改。使字段隐藏不会保护值不被更改。有人仍然可以更改隐藏字段并修改您不想修改的内容。请阅读这个最近的问题:我认为Model Binder能够通过其传递的ID找到某篇文章,填充每个字段本身(已经包含值),并且只修改视图中已编辑的每个字段。但显然(无论如何,我就是这样)如果它没有找到视图传递的该字段的输入,它将使用“默认”值填充该字段(例如,带有“01/01/0001 12:00:00am”的DateTime字段)。如果DateTime字段是DateCreated字段
// POST: /Article/Edit/5
[HttpPost]
public ActionResult Edit(Article article0)
{
var article = _db.Articles.Single(r => r.Id == viewModel.Id); // Grab the Article from the DB to update
article.Stickied = article0.Stickied;
// Fill in missing pieces
article.LastUpdatedBy = MyHelpers.SessionBag.Current.SamAccountName;
article.LastUpdated = DateTime.Now;
if (ModelState.IsValid)
{
_db.Entry(article0).State = EntityState.Unchanged;
_db.Entry(article).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(article0); // Something went wrong
}