没有一堆隐藏字段的成功模型编辑

时间:2012-06-07 16:19:37

标签: asp.net-mvc viewmodel model-binding valueinjecter

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

更新
所以我在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

为什么不将它们设置为数据库中当前设置的值?

5 个答案:

答案 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