使用MVC控制器的模型绑定更新编辑POST的关系

时间:2015-12-01 19:35:07

标签: asp.net asp.net-mvc entity-framework

使用具有多对多关系的Entity Framework,我很困惑如何从绑定模型的ASP.NET MVC控制器更新该关系。

例如,博客:帖子有很多标签,标签有很多帖子。

帖子控制器编辑操作无法延迟加载tags个实体:

public class PostsController : Controller
{
    [Route("posts/edit/{id}")]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Edit([Bind(Include = "Id,Title,Tags")] Post post)
    {
        // Post tags is null
        Post.tags.ToList();
    }
}

上面,HTTP Post将属性绑定到模型;但是,Post.tags关系为空。

我无法使用此.Include(p => p.Tags)查询帖子Attach()[Bind()]以检索相关标记实体。

在视图方面,我使用了一个tokenizer并传递了formdata - 我没有使用MVC列表组件。所以,问题是没有绑定视图formdata - 问题是.tags属性不是延迟加载实体。

这种关系是有用的 - 从Razor cshtml视图我能够遍历集合并查看子标签。

在Post View中,我可以查看标签(这可行)

@foreach (var tag in Model.Tags) {
}

在标签视图中,我可以查看帖子(此作品)

@foreach (var post in Model.Posts) {
}

同样,在Posts控制器的创建动作中,我可以创建新的标签实体并保持它们的关系。

public class PostsController : Controller
{
    [Route("posts/create")]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
    {
        string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();

        post.Tags = new HashSet<Tag>();

        Tag tag = new Tag();
        post.Tags.Add(tag);

        if (ModelState.IsValid)
        {
            db.posts.Add(post);
            await db.SaveChangesAsync();
            return RedirectToAction("Admin");
        }

        return View(post);
    }

除了编辑HTTP Post之外,这些关系无处不在。作为参考,它们被定义为:

public class Post
{
    public virtual ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public virtual ICollection<Post> Posts { get; set; }
}

仅在HTTP发布编辑操作时,我无法访问帖子的标签。

如何加载相关标签来改变该集合?

2 个答案:

答案 0 :(得分:0)

您的问题与Entity框架无关。当您从表单发布带有collection属性的model / viewmodel时,它基本上是一个问题,collection属性变为null。

您可以使用EditorTemplates解决此问题。

看起来您正在使用视图中由Entity框架生成的实体类。通常这不是一个好主意,因为现在您的UI层与EF实体紧密耦合。如果明天您想将您的数据访问代码实现从EF更改为其他原因会怎么样?

因此,让我们创建一些在UI层中使用的视图模型。 Viewmodels是简单的POCO类。视图模型将具有视图绝对需要的属性。不要只复制所有实体类属性并粘贴到视图模型中。

public class PostViewModel
{
    public int Id { set; get; }
    public string Title { set; get; }
    public List<PostTagViewModel> Tags { set; get; }
}

public class PostTagViewModel
{
    public int Id { set; get; }
    public string TagName { set; get; }
    public bool IsSelected { set; get; }
}

现在,在您的GET操作中,您将创建PostViewModel类的对象,初始化Tags集合并发送到视图。

public ActionResult Create()
{
    var v =new PostViewModel();
    v.Tags = GetTags();
    return View(v);
}
private List<PostTagViewModel> GetTags()
{
    var db = new YourDbContext();
    return db.Tags.Select(x=> new PostTagViewModel { Id=x.Id, TagName=x.Name})
         .ToList();

} 

现在,让我们创建一个编辑器模板。转到~/Views/YourControllerName目录并创建一个名为 EditorTemplates 的子目录。在那里创建一个名为PostTagViewModel.cshtml的新视图。

将以下代码添加到新文件中。

@model YourNamespaceHere.PostTagViewModel
<div>
    @Model.TagName
    @Html.CheckBoxFor(s=>s.IsSelected)
    @Html.HiddenFor(s=>s.Id)      
</div>

enter image description here

现在,在PostViewModel强类型的主视图(create.cshtml)中,我们将调用Html.EditorFor辅助方法来使用编辑器模板。

@model YourNamespaceHere.PostViewModel
@using (Html.BeginForm())
{
    <label>Post title</label>
    @Html.TextBoxFor(s=>s.Title)

    <h3>Select tags</h3>
    @Html.EditorFor(s=>s.Tags)

    <input type="submit"/>
}

现在,在您的HttpPost操作方法中,您可以检查Tags集合的已发布模型。

[HttpPost]
public ActionResult Create(PostViewModel model)
{
    if (ModelState.IsValid)
    {
       // Put a break point here and inspect model.
        foreach (var tag in model.Tags)
        {
            if (tag.IsSelected)
            {
                // Tag was checked from UI.Save it
            }
        }
        // to do : Save Post,Tags and then redirect.
    }
    model.Tags = GetTags(); //reload tags again 
    return View(model);
}

因为我们的HttpPost动作的参数是PostViewModel的一个对象,我们需要将它映射到你的Entity类,以使用实体框架保存它。

var post= new Post { Title = model.Title };
foreach(var t in model.Tags)
{
   if(t.IsSelected)
   {
      var t = dbContext.Tags.FirstOrDefault(s=>s.Id==t.Id);
      if(t!=null)
      { 
        post.Tags.Add(t);
      }
   }
}
dbContext.Posts.Add(post);
await dbContext.SaveChangesAsync();

答案 1 :(得分:0)

您的create方法缺少要插入标记的帖子的Id参数。 您必须在方法的开头声明Post Id。

[Route("posts/create")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
{
        Post nPost= db.Posts.FirstOrDefault(x => x.Id == post.Id);
        nPost.Id= post.Id;
        nPost.Title= post.Title;
        string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();
        foreach(string item in tagNames)
        {
          //First check if you got tag in Tags table
          Tag tg= db.Tags.FirstOrDefault(x => x.Name.ToLower() == item.ToLower().Trim());
          // If no row than create one.
          if (tg== null)
          {
             tg= new Tag();
             tg.Name= item;
             db.Tags.Add(tg);
             await db.SaveChanges();
           }
          // Now adding those tags to Post Tags.
           if (nPost.Tags.FirstOrDefault(x => x.Id == tg.Id) == null)
           {
              nPost.Tags.Add(etk);
              await db.SaveChanges();
           }
        }
        if (ModelState.IsValid)
        {
          await db.SaveChangesAsync();
          return RedirectToAction("Admin");
        }
    return View(post);
}

就是这样。