简而言之:如何成功编辑数据库条目,而无需在编辑视图中包含模型的每个字段?
更新
所以我在DB(文章)中有一个项目。我想编辑一篇文章。我编辑的文章有很多属性(Id,CreatedBy,DateCreated,Title,Body)。其中一些属性永远不需要更改(如Id,CreatedBy,DateCreated)。因此,在我的编辑视图中,我只想要可以更改的字段的输入字段(如标题,正文)。当我像这样实现编辑视图时,模型绑定失败。我没有提供输入的任何字段都设置为某个“默认”值(如DateCreated设置为01/01/0001 12:00:00 am)。如果我做为每个字段提供输入,一切正常,文章按预期编辑。我不知道说“模型绑定失败”是否正确,如果“如果在编辑视图中没有为它们提供输入字段,则系统会填充包含不正确数据的字段。”
如何以这样的方式创建编辑视图:我只需要为可以/需要编辑的字段提供输入字段,这样当调用Controller中的Edit方法时,会正确填充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>
. . .
如果我遗漏这两个输入:
@Html.HiddenFor(model => model.CreatedBy)
@Html.HiddenFor(model => model.DateCreated)
调用Edit方法时,它们被设置为默认值。 CreatedBy设置为 Null ,Created设置为 01/01/0001 12:00:00 am
为什么不将它们设置为数据库中当前设置的值?
答案 0 :(得分:10)
经过一些研究后,我发现了一些有助于ViewModel流程的工具 - 一个是AutoMapper&amp;其他InjectValues。我使用InjectValues主要是因为它不仅可以“展平”对象(地图对象a - &gt; b),而且还可以“展开”它们(地图对象b - &gt; a) - 不幸的是,AutoMapper缺少的东西不包括在内the-box - 我需要做的事情才能更新数据库中的值。
现在,我没有将我的文章模型及其所有属性发送到我的视图,而是创建了一个仅包含以下属性的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发送到View并使用其数据在DB中创建新文章。我们通过将ViewModel“unflattening”到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方法几乎相同,除了不向View发送新的ViewModel,我们发送一个预先填充了来自数据库的数据的ViewModel。我们通过从DB检索文章并将数据展平到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方法,我们希望获取从View发送的数据并使用它更新存储在DB中的文章。要做到这一点,我们只需将ViewModel“unflattening”到Article对象上即可反转展平过程 - 就像我们对Create方法的POST版本所做的那样:
//
// 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
}
我们还需要更改强类型的创建&amp;编辑视图以期望ArticleViewModel而不是文章:
@model ProjectName.ViewModels.ArticleViewModel
就是这样!
总而言之,您可以实现ViewModels,只将模型的件传递给您的视图。然后,您可以只更新这些部分,将ViewModel传递回Controller,并使用ViewModel中的更新信息来更新实际模型。
答案 1 :(得分:2)
查看模型示例:
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");
}
答案 2 :(得分:2)
没有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
}
答案 3 :(得分:0)
使用 ViewModels 。
通过我继续研究找到这个问题的解决方案,我相信使用这些名为“ViewModels”的东西是可行的方法。正如Jimmy Bogard在post中所解释的那样,ViewModels是一种“显示来自单个实体的信息片段的方式。”
asp.net-mvc-view-model-patterns让我走上正轨;我还在查看作者发布的一些外部资源,以便进一步掌握ViewModel概念(Jimmy的博客文章就是其中之一)。
答案 4 :(得分:0)
除了答案之外,AutoMapper还可以用来解开它。 Using AutoMapper to unflatten a DTO