Sunday, January 15, 2012

Simple Circles–Add an ASP.NET MVC 3 UI (Part 4b)

This post is part of a series on building a simple to use web-based contact and customer relationship management application. The goal is to support various audiences including businesses, teams, clubs, religious organizations, etc.
Simple Circles - Ninject packageIn the last post I left off with a scaffolded People MVC implementation that was hard-wired to use the Entity Framework 4 data access technology. In this post I’m going to replace that with the Ninject dependency-injection framework and use the previously built FakePartyRepository implementation to begin testing the UI quickly.
With NuGet, adding Ninject support is easy – just two simple commands: ‘Install-Package Ninject -Version 2.2.1.4’ and ‘Install-Package Ninject.MVC3 -Version 2.2.2.0’. I chose to include specific versions since they were specified on the corresponding NuGet Gallery pages. Once again there is a lot going on behind the scene as shown to the right.
Simple Circles - Ninject.MVC3The Ninject.MVC3 extension was created to add support to MVC 3 applications including DI for Controllers, filters, validators and the Unit of Work pattern for NHibernate (more on this later!). The NuGet version adds a class to the \App_Start folder called NinjectMVC3 wired and ready to go. It does so by hooking in with the WebActivator project – one of the dependencies automatically installed when the Ninject.MVC3 package was installed.

Controller

First I’ll add references to the Domain and Persistence projects to bring in their type definitions then I’ll replace the EF context reference in PeopleController with the IPartyRepository instead. Refer to the last screenshot in the previous post to see the boilerplate code generated by the scaffolding.
public class PeopleController : Controller
{
  private readonly IPartyRepository repository;

  public PeopleController(IPartyRepository repository)
  {
    this.repository = repository;
  }

  public ViewResult Index()
  {
    IEnumerable<Domain.Person> persons = this.repository.FindAllPersons();
    return View(persons);
  }
  ...

The problem with the above code is that it will not compile because of line 13 – ‘return View(persons)’. The MVC convention is to use separate model classes in the presentation layer to decouple it from the business/domain layer. I did exactly this when creating a Person class in the \Models folder - essentially flattening the EntityBase->Entity->Party->Person inheritance hierarchy to just Person. The scaffolding constructed the views (Index, Details, Edit, etc.) to work with the Models.Person class – not the Domain.Person. Line 12 is returning an enumeration of Domain.Person instances whereas the view expects Models.Person instances.

Data Mapper


The solution to this mismatch lies in another tool called AutoMapper which makes using the Data Mapper pattern a cinch. Jeremy Miller’s MSDN article Persistence Patterns gives a good overview of how these various proven patterns fit and work together. Also, Jimmy Bogard wrote a series of articles on his tool at Los Techies. Back to the NuGet Package Manager console to ‘Install-Package AutoMapper’.

I created a \Maps folder and added a new class called PersonMap to define the mapping back and forth between the two models…
internal class PersonMap
{
  internal static void CreateMaps()
  {
    Mapper.CreateMap<Domain.Person, Models.Person>()
        .ForMember(dest => dest.Birthday, opt => opt.MapFrom(src => src.Birthday))
        .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
      ...
        .ForMember(dest => dest.PersonId, opt => opt.MapFrom(src => src.PartyId))
        .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.PartyName))
        .ForMember(dest => dest.Salutation, opt => opt.MapFrom(src => src.Salutation ?? null))
        .ForMember(dest => dest.Suffix, opt => opt.MapFrom(src => src.Suffix ?? null));

    Mapper.CreateMap<Models.Person, Domain.Person>()
        .ForMember(dest => dest.ChannelAddresses, opt => opt.Ignore())
        .ForMember(dest => dest.Birthday, opt => opt.MapFrom(src => src.Birthday))
        .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
      ...
        .ForMember(dest => dest.PartyId, opt => opt.MapFrom(src => src.PersonId))
        .ForMember(dest => dest.PartyName, opt => opt.Ignore())
        .ForMember(dest => dest.PartyType, opt => opt.Ignore())
        .ForMember(dest => dest.PostalAddresses, opt => opt.Ignore())
        .ForMember(dest => dest.Salutation, opt => opt.MapFrom(src => src.Salutation ?? null))
        .ForMember(dest => dest.Suffix, opt => opt.MapFrom(src => src.Suffix ?? null));
  }
}
A few things are worth pointing out in the above listing:

  1. Line #9 shows how I map the generic, internal PartyId from the domain model to the more friendly and meaningful PersonId in the MVC layer.
  2. Line #10 does the same for the PartyName - mapping it to a FullName property.
  3. Line #15 tells the mapper to ignore the ChannelAddress property in the domain model – it will not be mapped to the MVC layer. The same goes for PostalAddresses  –  these will be dealt with later and in a manner appropriate for the user interface.
  4. Lines #11, #12, #23, #24 show how optional fields are handled.

A call to PersonMap.CreateMaps() in Global.asax.cs will initialize the maps when the website starts up. To use the maps, add a line to invoke the data mapping like this:
[HttpPost]
public ActionResult Create(Person person)
{
  if (!ModelState.IsValid)
  {
    return View(person);
  }

  var entity = Mapper.Map<Person, Domain.Person>(person);

  this.repository.Add(entity);

  return RedirectToAction("Index");
}

Line #9 is the magic – transferring the contents of the MVC Models.Person to the Domain.Person before passing to the repository to store.

Dependency Injection


The PeopleController at the top of this article used constructor injection on Line #5 to receive an IPartyRepository instance. Following the Ninject samples and documentation, I’ve added a \Services\ServicesModule which inherits from NinjectModule to handle the injection of services:
/// <summary>
/// A Ninject module to bind services.
/// </summary>
public class ServicesModule : NinjectModule
{
  /// <summary>
  /// Called when the module loads into the kernel.
  /// </summary>
  public override void Load()
  {
    this.Bind<IPartyRepository>()
        .To<FakePartyRepository>()
        .InRequestScope();
  }
}

To wire up the module, add a line to the RegisterServices method found in NinjectMVC3:
private static void RegisterServices(IKernel kernel)
{
  kernel.Load<Services.ServicesModule>();
}

The end result of this effort is that I can now browse and display the list of persons as shown here:

Simple Circles - MVC with Fake Repo

Next time I’ll set up a unit test to exercise the functionality of the controller through injection so that I won’t have to always manually hit every view/page to see if it still works as changes are made.

The source code for this article can be downloaded from here.

No comments:

Post a Comment