11

简而言之:如何成功编辑数据库条目,而无需在编辑视图中包含模型的每个字段?

更新
所以我在数据库中有一个项目(一篇文章)。我想编辑一篇文章。我编辑的文章有很多属性(Id、CreatedBy、DateCreated、Title、Body)。其中一些属性永远不需要更改(如 Id、CreatedBy、DateCreated)。所以在我的编辑视图中,我只想要可以更改的字段(如标题、正文)的输入字段。当我实现这样的编辑视图时,模型绑定失败。我没有提供输入的任何字段都设置为某个“默认”值(例如 DateCreated 设置为 01/01/0001 12:00:00am)。如果我这样做为每个字段提供输入,一切正常,文章按预期编辑。我不知道说“模型绑定失败”是否正确,就像“如果在编辑视图中没有为它们提供输入字段,系统会用不正确的数据填充字段”。

如何以只需要为可以/需要编辑的字段提供输入字段的方式创建编辑视图,以便在调用 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:00am

为什么它们没有设置为当前在数据库中设置的值?

4

5 回答 5

11

经过更多研究后,我发现了一些有助于 ViewModel 流程的工具——一个是 AutoMapper,另一个是 InjectValues。我选择 InjectValues 主要是因为它不仅可以“展平”对象(映射对象 a -> b),而且还可以“取消展平”它们(映射对象 b -> a)——不幸的是 AutoMapper 缺乏的东西- box - 为了更新数据库中的值,我需要做的事情。

现在,我没有将我的 Article 模型及其所有属性发送到我的视图,而是创建了一个仅包含以下属性的 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 并使用其数据在数据库中创建新文章。我们通过将 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 到 View 之外,我们发送一个预先填充了我们数据库中的数据的 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 方法,我们希望获取从视图发送的数据并用它更新存储在数据库中的文章。为此,我们只需通过将 ViewModel 'unflatten' 到 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
    }

我们还需要更改强类型的 Create & Edit 视图以期望 ArticleViewModel 而不是 Article:

@model ProjectName.ViewModels.ArticleViewModel

就是这样!

所以总而言之,您可以实现 ViewModel 以将模型的一部分传递您的视图。然后,您可以只更新这些部分,将 ViewModel 传递回 Controller,并使用 ViewModel 中的更新信息来更新实际模型。

于 2012-06-11T17:31:40.110 回答
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");   
}
于 2012-06-07T17:39:46.687 回答
2

另一个没有视图模型的好方法

// 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
}
于 2015-08-02T06:59:17.173 回答
0

使用视图模型

通过我对寻找解决这个问题的持续研究,我相信使用这些称为“ViewModels”的东西是要走的路。正如 Jimmy Bogard 在一篇文章中所解释的,ViewModel 是一种“显示来自单个实体的信息片段”的方法。

asp.net-mvc-view-model-patterns让我走上了正轨;我仍在查看作者发布的一些外部资源,以进一步掌握 ViewModel 概念(Jimmy 的博客文章就是其中之一)。

于 2012-06-08T14:47:43.520 回答
0

In addition to the answer, AutoMapper can also be used to unflatten it. Using AutoMapper to unflatten a DTO

于 2015-08-28T10:19:13.907 回答