Thursday, March 15, 2012

ASP.NET MVC - Annotating domain objects from external assemblies to support Validation using AutoMapper

The MVC paradigm requires you to provide Models to View in order to render dynamic data.  When creating a View that can create a new object, it's nice to use the built in Validation.  This allows Controllers to check if user input is valid before passing it down to a repository.

In a small single project MVC application all of the Models are defined once in the scope of the application and can be used in presentation, business, and data layers:


   1:  public class MyDomainObjectModel{
   2:     [Required]
   3:     public string Name {get;set;}
   4:  }

If this Model was passed to a View that would ask the user to populate the Model and then the View would pass it to the Controller, Validation would ensure the user entered a Name, because the Name property has been annotated with the Required attribute.

I ran into a problem, however, when I setup my project to have separate assemblies for presentation and for data repositories.  My domain objects were defined in my data repository assembly as the repositories needed to know the objects they would be storing.  The data repository assembly doesn't reference System.Web.Mvc, and why should it, it's not concerned with any presentation.  Consequently, it doesn't make sense to add Mvc annotations to the domain objects created in the data repository.  To put this explanation in a different light: perhaps the objects are meant to be reused by other UIs that would not be able to take advantage of the Mvc annotations, or perhaps you don't have access to the assembly where the domain objects are defined.

So now I want to be able to use domain objects as Models for my Views.  My initial thought was to define Models as subclasses of my domain objects:


   1:   namespace Example.Core.DomainObjects{
   2:     public class DomainObject{
   3:        public string virtual Name {get;set;}
   4:     }
   5:  }
   6:   
   7:  namespace Example.Mvc.Models{
   8:     public class DomainObjectModel{
   9:     [Required]
  10:     public override string Name{get{ return base.Name;} set{base.Name = value;}}
  11:     }
  12:  }

Note:  My domain objects are all virtual because I'm using NHibernate, but if they weren't, using the new property to indicate hiding should also work.

Also, my repository looked like this:

   1:  public class DomainObjectRepository(){
   2:     public void Save(DomainObject){}
   3:     public DomainObject GetByID(int id){}
   4:  }

So this solution worked well for using Mvc to create objects:

   1:  public ActionResult Create()
   2:  {
   3:     return View(new  DomainObjectModel ());
   4:  }
   5:   
   6:  [HttpPost]
   7:  public ActionResult Create(DomainObjectModel model)
   8:  {
   9:      if (ModelState.IsValid)
  10:      {
  11:           new DomainObjectRepository().Save(model);
  12:           return RedirectToAction("Index");
  13:      }
  14:   
  15:      return View("Create", model);
  16:  }

.NET automatically down casts my DomainObjectModel to a DomainObject and my DomainObjectRepository has no problem saving it.  A problem starts to creep up when I need to edit the model:


   1:  public ActionResult Edit(int id)
   2:  {
   3:     //Option 1 - Send a DomainObject to View
   4:     return View(new DomainObjectRepository().GetByID(id));
   5:   
   6:     //Option 2 - Try Upcasting DomainObject to DomainObjectModel
   7:     return View( (DomainObjectModel) new DomainObjectRepository().GetByID(id));
   8:  }

My repository only knows how to return a DomainObject.  If I pass that directly to my View, I would lose the Validation that I built into the model.  If I try and cast the DomainObject to a DomainObjectModel, that fails as upcasting isn't supported in .NET.

Instead, I'd need to manually create a way to map a populated DomainObject to a DomainObjectModel, perhaps:

   1:  public class DomainObjectModel : DomainObject{
   2:     public DomainObjectModel(){}
   3:   
   4:     public DomainObjectModel(DomainObject obj)
   5:    {
   6:        this.Name = obj.Name;
   7:    }
   8:   
   9:     [Required]
  10:     public override string Name{get{ return base.Name;} set{base.Name = value;}}
  11:  }

While, this isn't a huge deal for one Model, that has one property, but this could become a maintenance head ache once the number of Models and their properties increased, and really, there's no reason that this kind of code should need to be hand written.

Instead, I decided to use a tool call AutoMapper (http://automapper.org/).  This tool, will auto-magically perform the conversions I need:

   1:  public ActionResult Edit(int id)
   2:  {
   3:     //Option 3 - Use AutoMapper!
   4:     return View(AutoMapper.Mapper.Map<DomainObject, DomainObjectModel>(
   5:           new DomainObjectRepository().GetByID(id)));
   6:  }

This only slightly clutters the code, but now I no longer have to manually write my mapping code!  Of course, I still need to configure AutoMapper, so it knows that it can map these objects.  Since I'm in Mvc I did this in my global.asax:


   1:  protected override void OnApplicationStarted()
   2:  {
   3:      //In my actual application, I moved this to a separate file, so I didn't clutter
   4:      //my global.asax with all of the mappings.
   5:      AutoMapper.Mapper.CreateMap<DomainObject,DomainObjectModel>();
   6:  }

After that, everything is good to go.

One note on AutoMapper, I've found that when you specify a mapping, it's a one way relationship.  If the only Map specified is the one above mapping a DomainObject to a DomainObjectModel, I would not be able to use AutoMapper to map a DomainObjectModel to a DomainObject.  AutoMapper would instead just return my original object:

   1:  public void ExampleMethod()
   2:  {
   3:      DomainObject dO = new DomainObject();
   4:      DomainObject doM = new DomainObjectModel();
   5:   
   6:      //Assuming no existing mappings.
   7:      AutoMapper.Mapper.CreateMap<DomainObject,DomainObjectModel>();
   8:   
   9:      var mappedObject = AutoMapper.Mapper.Map<DomainObject,DomainObjectModel>(doM);
  10:   
  11:      //mappedObject is of type DomainObject - Mapping worked.
  12:   
  13:      mappedObject = AutoMapper.Mapper.Map<DomainObjectModel, DomainObject>(dO);
  14:      
  15:      //mappedObject is of type DomainObject - Mapping failed! - Mapper doesn't know how
  16:      //to convert a DomainObject into a DomainObjectModel
  17:   
  18:      AutoMapper.Mapper.CreateMap<DomainObjectModel, DomainObject>();
  19:   
  20:      mappedObject = AutoMapper.Mapper.Map<DomainObjectModel, DomainObject>(dO);
  21:   
  22:      //mappedObject is of type DomainObjectModel - Mapping worked.
  23:  }

Sunday, March 11, 2012

ASP.NET MVC 3, Tracing, and Log4Net AspNetTraceAppender

I set up a ASP.NET MVC 3 application that uses log4net as the logging infrastructure.  It is convenient to be able to view logging information on the page I am working on, as opposed to having to switch back and forth between browsers and log files.  In Web Forms applications I accomplished this by configuring log4net to use the AspNetTraceAppender  and enabled tracing in the web.config which would display logging information as part of the trace diagnostics information.  However, this does not work in MVC as it seems to use a different tracing infrastructure.  From http://stackoverflow.com/questions/3328678/asp-net-mvc-tracing-issues quoting "MVC 2 in action":

When you called Trace.Write() in Web Forms, you were interacting with the Trace- Context class. This exists on your ViewPage in ASP.NET MVC, but this isn’t where you would want to write tracing statements. By the time you’ve passed the baton over to the view, there’s no logic there that you’d need to trace. Instead, you’d like to trace the logic embedded in your controllers. You might try to leverage the TraceContext class in your controller, but these statements won’t ever make their way to the list of messages in the trace log (on your page or on Trace.axd). Instead, you can use System.Diagnostics.Trace and set up your own TraceListeners to inspect the activity in your controllers. Alternatively, you can leverage a more mature logging framework such as log4net or NLog:
You debug ASP.NET MVC applications just as you would any .NET application. Tracing, however, doesn’t offer as much for MVC. Instead, you can lean on the built-in TraceListeners in .NET, or utilize a good logging library like those mentioned earlier. Another aspect of error logging is health monitoring.
Per the stackoverflow post, I installed glimpse .  With tracing enabled in the web.config, glimpse running, and the AspNetTraceAppender configured everything worked; my log messages were displayed in the browser!