【问题标题】:AutoMapper on complicated Views (multiple looping)复杂视图上的 AutoMapper(多循环)
【发布时间】:2011-12-17 04:13:18
【问题描述】:

今天早些时候,一位乐于助人的人(在 Stack Overflow 上)将我指向AutoMapper,我查看了它,我非常喜欢它!但是现在我有点卡住了。

在我的 Code First MVC3 应用程序中,在我的 [Home/Index] 上,我需要显示来自我的实体的以下信息:

  1. 帖子列表 [ int Id, string Body, int Likes, string p.User.FirstName, string p.User.LastName ]​​i>
  2. 标签列表 [int Id, string Name]
  3. 我的数据库中存在的所有作者列表 [ string UrlFriendlyName ]​​i>

到目前为止,通过对我的 Index ViewModel 执行以下操作,我只管理了列表中的第 1 点:

public class IndexVM
{
    public int Id { get; set; }
    public string Body { get; set; }
    public int Likes { get; set; }
    public string UserFirstName { get; set; }
    public string UserLastName { get; set; }
}

在 Home 控制器上,我有 Index ActionMethod:

public ActionResult Index()
{
    var Posts = postsRepository.Posts.ToList();
    Mapper.CreateMap<Post, IndexVM>();
    var IndexModel = Mapper.Map<List<Post>, List<IndexVM>>(Posts);
    return View(IndexModel);
}

最后在我的视图中,我将它强类型化为:

@model IEnumerable<BlogWeb.ViewModels.IndexVM>

我通过以下方式将 IndexVM IEnumberable 中的每个项目传递给部分视图:

@foreach (var item in Model)
{
    @Html.Partial("_PostDetails", item)
}

我的问题是,我怎样才能同时达到第 2 点和第 3 点,同时又不破坏我在第 1 点中取得的成就。

我尝试将我目前为 IndexVM 拥有的东西放入一个子类中,并在父类上拥有一个列表属性,但它不起作用。

【问题讨论】:

    标签: asp.net-mvc-3 entity-framework-4.1 ef-code-first automapper


    【解决方案1】:

    来自 ASP.NET MVC2 In Action Book:

    有些屏幕比单个表格更复杂。它们可能具有 多个表格和其他数据的附加字段:图像、标题、 小计、图表、图表和其他一百万个复杂的东西 一个看法。演示模型解决方案可扩展以处理所有这些问题。 开发人员可以自信地维护即使是最粗糙的屏幕只要 因为演示模型设计得很好。如果屏幕确实包含 多个复杂元素,表示模型可以是包装器, 将它们全部组合起来并减轻标记文件的复杂性。一种 好的展示模型不会隐藏这种复杂性——它代表了它 尽可能准确和简单,并将数据分开 屏幕。

    制作一个代表您的屏幕的 ViewModel。然后构建它并将其传递给视图。 This 这本书很棒,它谈到了使用演示模型。使用 AutoMapper,想想如果没有它,您将如何完成映射,然后使用它。 AutoMapper 不会做任何神奇的事情,它消除了敲击键盘。

    AutoMapper 放在一边,带上您的需求清单:

    1. 帖子列表 [ int Id, string Body, int Likes, string p.User.FirstName, string p.User.LastName ]
    2. 标签列表 [int Id, string Name]
    3. 我的数据库中存在的所有作者列表 [ 字符串 网址友好名称]

    并假设您有以下模型实体:帖子、标签、作者

    我个人不喜欢将模型实体传递给我在 MVC 或 MVVM 中的演示文稿,但这就是我。假设我们在这里遵循它并创建 PostDisplay、TagDisplay 和 AuthorDisplay。

    根据 View 的要求,ViewModel 将如下所示:

    Public class IndexVM
    {
        Public List<PostDisplay> Posts {get; set;}
        Public List<TagDisplay> Tags {get; set;}
        Public List<AuthorDisplay> Authors {get; set;}
    }
    

    在这种情况下,视图的组成方式将需要您构建它:

    public ActionResult Index()
    {
        var posts = postsRepository.Posts.ToList();
        var tags = postsRepository.Tags.ToList();
        var authors = postsRepository.Authors.ToList();
    
        Mapper.CreateMap<Post, PostDisplay>();
        Mapper.CreateMap<Tag, TagDisplay>();
        Mapper.CreateMap<Author, AuthorDisplay>();
    
        private var IndexVM = new IndexVM
        {
            Posts = Mapper.Map<List<Post>, List<PostDisplay>>(posts),
            Tags = Mapper.Map<List<Tag>, List<TagDisplay>>(tags),
            Authors = Mapper.Map<List<Author>, List<AuthorDisplay>>(authors)
        };
    
        return View(IndexVM);
    }
    

    因此,您最终得到的是一个 ViewModel 传递给您的视图,它准确地代表您想要显示的内容,并且与您的域模型不紧密耦合。我想不出一种方法让 AutoMapper 将三个单独的结果列表映射到一个对象中。

    为了澄清,AutoMapper 会将子集合映射为如下结构:

    public class OrderItemDto{}
    public class OrderDto
    {
        public List<OrderItemDto> OrderItems { get; set; }
    }
    

    将映射到:

    public class OrderItem{}
    public class Order
    {
        public List<OrderItem> OrderItems { get; set; }
    }
    

    只要告诉它如何映射类型:OrderDto -> Order 和 OrderItemDto -> OrderItem。

    【讨论】:

    • 谢谢您的回复,德里克。我已经按照你的建议做了(PasteBin Here)。但我现在不知道如何继续使用 AutoMapper。我如何告诉 AutoMapper 以我的 ViewModel 现在的方式来做它的事情?谢谢
    • 已更新,还请查看manning.com/palermo2 这本书有一个关于演示模型的精彩章节。
    • 只是让你知道我不小心投了反对票而不是赞成票。现在,downvote 在没有编辑的情况下被锁定。如果您进行编辑,我会将投票更改为赞成
    • 恐怕仍然无法正常工作 :( 我在following code 的第一行得到了 [AutoMapper.AutoMapperMappingException]。我们之前不需要做 [Mapper.CreateMap]做 Mapper.Map 吗?至少视频显示了这个,但仍然不知道如何配置它。;'(
    • 是的,您只需要创建一次地图。您可以在某种引导程序中或使用配置文件来执行此操作。引导程序 goo.gl/B58n9goo.gl/zBwyp 调用
    【解决方案2】:

    作为在单个视图模型中包含所有实体列表的替代方法,您可以使用@Html.Action。然后,在您的屏幕视图中:

    @Html.Action("Index", "Posts")
    @Html.Action("Index", "Tags")
    @Html.Action("Index", "Authors")
    

    这样,您的索引/屏幕视图和模型不需要了解其他视图模型。部分由单独控制器上的单独子操作方法传递。

    所有自动映射器的东西仍然适用,但您仍然需要将实体单独映射到视图模型。不同之处在于,不是在 HomeController.Index() 中进行映射,而是在 PostsController.Index()、TagsController.Index() 和 AuthorsController.Index() 中进行。

    回复评论 1

    public class IndexVM
    {
        // need not implement anything for Posts, Tags, or Authors
    }
    

    然后,在 3 个不同的控制器上实现 3 种不同的方法。这是 PostsController 的一个示例。对 TagsController 和 AuthorsController 遵循相同的模式

    // on PostsController
    public PartialViewResult Index()
    {
        var posts = postsRepository.Posts.ToList();
    
        // as mentioned, should do this in bootstrapper, not action method
        Mapper.CreateMap<Post, PostModel>();
    
        // automapper2 doesn't need source type in generic args
        var postModels = Mapper.Map<List<PostModel>>(posts); 
        return PartialView(postModels);
    }
    

    您必须为此创建一个相应的局部视图,强类型为@model IEnumerable&lt;BlogWeb.ViewModels.PostModel&gt;。在该视图中,放置呈现 Posts UI 的 HTML(从您的 HomeController.Index 视图移动)。

    在您的 HomeController 上,只需执行以下操作:

    public ActionResult Index()
    {
        return View(new IndexVM);
    }
    

    在 IndexVM 上保持视图的强类型

    @model IEnumerable<BlogWeb.ViewModels.IndexVM>
    

    ...然后像这样获取帖子、标签和作者:

    @Html.Action("Index", "Posts")
    

    对评论 2 的回应

    引导...您的 Mapper.CreateMap 配置只需在每个应用程序域发生一次。这意味着您应该从 Application_Start 执行所有 CreateMap 调用。将它们放在控制器代码中只会产生不必要的开销。当然,需要创建地图 - 但不是在每个请求期间。

    这也有助于单元测试。如果将所有 Mapper.CreateMap 调用放入单个静态方法中,则可以从单元测试方法以及 Global.asax Application_Start 调用该方法。然后在单元测试中,一种方法可以测试您的 CreateMap 调用是否设置正确:

    AutoMapperBootStrapper.CreateAllMaps();
    Mapper.AssertConfigurationIsValid();
    

    【讨论】:

    • 谢谢 OliveHour。我现在明白了。它有点像小部件一样创建,您可以将其放置在您想要的任何位置。这对我来说非常好,因为标签和用户是侧边栏中的小部件。太感谢了。我仍然不明白 Bootstrapping 的重要性。你能简要解释一下,为什么需要它吗?再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多