Sunday, August 14, 2011

Flattening!

I'm messing around with AutoMapper and trying to figure out how to make it flatten across two one-to-many relationships (i.e. parents, children, and grandchildren collapsed into one flat format) instead of just one... but I'm not going to be able to figure it out this morning. I'm sleepy from being up all night. I thought I'd give a posting that is just a quick overview of AutoMapper and then leave AMD as the sun comes up. It's time for IHOP. Anyways... I’m "expanding" upon the code base in this posting and this posting too. I have replaced this line in the Person object...

public Address Home { get; set; }

 
 

...with this line...

public Address[] Homes { get; set; }

 
 

...my Controller now looks like so...

using System;

using System.Web.Mvc;

using Core;

using MyApp.Models;

namespace MyApp.Controllers

{

   public class HomeController : DefaultController

   {

      public ActionResult Index()

      {

         Address austin = new Address();

         austin.Id = new Guid("75b5e18c-13c5-4211-bf8d-9f3d0117d88c");

         austin.Street = "1507 Houston Street #145";

         austin.ZipCode = 78756;

         

         Address haverhill = new Address();

         haverhill.Id = new Guid("bb61cd17-c176-4102-838a-9f3d0117d892");

         haverhill.Street = "5055 Club Road";

         haverhill.ZipCode = 33415;

         

         Address houston = new Address();

         houston.Id = new Guid("31ba86cb-991f-4e71-a2df-9e4000a8b3bc");

         houston.Street = "14795 Memorial Drive";

         houston.ZipCode = 77079;

         

         Address lampasas = new Address();

         lampasas.Id = new Guid("d74e7f2c-ad8d-4522-bb1e-9f3d0117d895");

         lampasas.Street = "1006 East Avenue F";

         lampasas.ZipCode = 76550;

         

         Address[] addresses = new Address[] {austin, haverhill, houston, lampasas};

         

         Person person = new Person();

         person.Id = new Guid("705b5d2b-6bed-4d6d-af29-9e4000a8b25e");

         person.Name = "Tom";

         person.setBirthDay(new DateTime(1974, 8, 24));

         person.Homes = addresses;

         

         return AutoMapView<PersonShowModel>(View(person));

      }

   }

}

 
 

...my View Model looks like so...

using System;

namespace MyApp.Models

{

   public class PersonShowModel

   {

      public string Name { get; set; }

      public DateTime BirthDay { get; set; }

      public int Age { get; set; }

      public AddressModel[] Homes { get; set; }

      

      public class AddressModel

      {

         public string Street { get; set; }

         public Int32 ZipCode { get; set; }

      }

   }

}

 
 

...and the View itself looks like so...

@model MyApp.Models.PersonShowModel

<table>

@foreach (var item in Model.Homes)

{

   <tr>

      <td>@Model.Name</td>

      <td>@Model.Age</td>

      <td>@Model.BirthDay</td>

      <td>@item.Street</td>

      <td>@item.ZipCode</td>

   </tr>

}

</table>


 
 

OK. What are the steps to wire this up?

  1. Add a reference to the AutoMapper .dll

     
     

  2. In Application_Start() in Global.asax.cs put:

    AutoMapperBootstrapper.Initialize();

     
     

  3. Create a "Helpers" folder if you do not yet have one.

     
     

  4. Put <add namespace="MyApp.Helpers"/> in the </namespaces> portion of Web.config if it is not yet there. (this namespace trick may be what Byron is utilizing here/see the last paragraph)

     
     

  5. AutoMapperViewResults.cs (one of two files that you must create in the Helpers folder) is going to look like this:

    using System;

    using System.Web.Mvc;

    using AutoMapper;

    namespace MyApp.Helpers

    {

       public class AutoMapViewResult : ActionResult

       {

          public Type SourceType { get; private set; }

          public Type DestinationType { get; private set; }

          public ViewResultBase View { get; private set; }

          

          public AutoMapViewResult(Type source, Type dest, ViewResultBase view)

          {

             SourceType = source;

             DestinationType = dest;

             View = view;

          }

          

          public override void ExecuteResult(ControllerContext context)

          {

             var model = Mapper.Map(View.ViewData.Model,

                SourceType,

                DestinationType

                );

             View.ViewData.Model = model;

             View.ExecuteResult(context);

          }

       }

    }

     
     

  6. AutoMapperBootstrapper.cs (the other file that you must create in the Helpers folder) is going to vary from application to application. In this case it looks like this:

    using AutoMapper;

    using Core;

    using MyApp.Models;

    namespace MyApp.Helpers

    {

       public static class AutoMapperBootstrapper

       {

          public static void Initialize()

          {

             Mapper.Initialize(cfg =>

             {

                cfg.AddProfile<PersonShowModelProfile>();

             });

          }

       }

       

       public class PersonShowModelProfile : Profile

       {

          protected override void Configure()

          {

             CreateMap<Person, PersonShowModel>();

             CreateMap<Address, PersonShowModel.AddressModel>();

          }

       }

    }

     
     

  7. Here is another great example of extending MVC. If AutoMapViewResult is bright red in Visual Studio it means that you are still inheriting from Controller at your Controller instead of DefaultController which is a new file that you will need which looks like this:

    using System.Web.Mvc;

    using MyApp.Helpers;

    namespace MyApp.Controllers

    {

       public class DefaultController : Controller

       {

          protected AutoMapViewResult AutoMapView<TDestination>(

             ViewResultBase result

             )

          {

             return new AutoMapViewResult(

                result.ViewData.Model.GetType(),

                typeof(TDestination), result

                );

          }

       }

    }

     
     

  8. The only other thing I can think to mention is a curious problem that is touched on at the bottom of the comments on this post by Matt Hinze where Matt refers the individual asking over to this post by Jimmy Bogard. The problem is of seeing this error:

    The model item passed into the dictionary is of type 'Something' but this dictionary requires a model item of type 'SomethingElse'.

    For me, the issue had nothing to do with AutoMapper. I had simply screwed up the model binding in a View while incorporating AutoMapper. I had a Controller handing in one model and the View trying to consume a different model. This mistake causes this error. I jumped to the conclusion that the problem was of AutoMapper, but if you have this problem you may want to take another look.

No comments:

Post a Comment