Monday, October 15, 2012

MVC: organize your models properly

ASP.NET MVC is an excellent framework for developing scalable, testable and flexible Web applications. But bad approach can lead to bloated Controllers, poorly organized Views and chaos in Models. This article focuses on the approach for organization of models that is used successfully in several large web projects.
Consider, for example, a page which contains a large amount of information generated on server. This information may come from various sources. Following MVC paradigm we have to prepare a model that contains all necessary information and bind it to the appropriate view for display.

Consider HomeController:
 public class HomeController : BaseController  
   {  
     private readonly HomeIndexViewDataBuilder _homeIndexViewDataBuilder;  
     private readonly HomeInfoViewDataBuilder _homeInfoViewDataBuilder;  
     public HomeController(Context context, HomeIndexViewDataBuilder homeIndexViewDataBuilder, HomeInfoViewDataBuilder homeInfoViewDataBuilder)  
       : base(context)  
     {  
       _homeIndexViewDataBuilder = homeIndexViewDataBuilder;  
       _homeInfoViewDataBuilder = homeInfoViewDataBuilder;  
     }     
     public ActionResult Index()  
     {        
       var model = _homeIndexViewDataBuilder.Build();        
       return View(model);      
     }  
     public ActionResult Info(string id)  
     {  
       var model = _homeInfoViewDataBuilder.Build(id);  
       if (model!= null)  
        return View(model);  
       return TransferToNotFound();  
     }            
   }  

In order not to bloat controllers, preparation of model with necessary information takes place in Builder. In our convention Builder is a class that corresponds 1:1 to Controller-Action method and contains a single method Build. We use the following naming convention: if controller is named HomeController and Action-method is named Index then correspond Builder, will be called HomeIndexViewDataBuilder. It may look like this:
 public class HomeIndexViewDataBuilder : BaseModelBuilder  
   {  
    private readonly TopicQuery _topicQuery;  
     public HomeIndexViewDataBuilder(IModelCacher modelCacher, TopicQuery topicQuery): base(modelCacher)  
     {  
       _topicQuery = topicQuery;  
     }  
     public HomeIndexViewData Build()  
     {  
       var homeIndexViewData = new HomeIndexViewData()  
       /*filling model(fetching data from database, cache or other builders)*/  
       return homeIndexViewData;  
     }  
   }  

You can injects any objects in your Builder. In this example, objects of the following classes are injected: ModelCacher is a class for caching models, TopicQuery is a class used to fetch news from database. In some cases parameters to Build method can be passed (eg, news ID or something else). Build method returns a model that contains all necessary information to be displayed on the home page. This model does not contain any logic, only flat data for display. It may look like this:
 public class HomeIndexViewData  
   {  
     public IDictionary<string , NewsTeaserViewModel []> Teasers { get; set; }      
     public NewsTeaserViewModel[] Top { get ; set ; }      
     public NewsTeaserViewModel[] EditorsChoice { get ; set ; }      
     public NewsTeaserViewModel[] MostViewed { get ; set ; }      
     public NewsTeaserViewModel[] MostCommented { get ; set ; }      
     public NewsGalleryTeaserViewModel[] LastGalleries { get ; set ; }  
   }  

Class name follows our naming convention. That is, if Builder is called HomeIndexViewDataBuilder, then model will be HomeIndexViewData. When we say ViewData we mean a composite model. This composite model consists of variety of models, such as: NewsTeaserViewModel and NewsGalleryTeasersViewModel. By ViewModel we mean a simple model. For example, NewsTeaserViewModel may look like:
 public class NewsTeaserViewModel  
   {  
     public Guid Id { get; set; }  
     public string Url { get; set; }  
     public string Title { get; set; }  
     public string Description { get; set; }  
     public Guid? Image { get; set; }  
     public string Rubric { get; set; }  
     public DateTime? PublicationDate { get; set; }  
     public uint Comments { get; set; }  
  }  

Differences between ViewData and ViewModel:


ViewData - is a composite model returned by Builder. Builder corresponds 1:1 to a Controller-Action method. For example: HomeIndexViewDataBuilder returns HomeIndexViewData, HomeInfoViewDataBuilder returns HomeInfoViewData etc. These models are used only in correspond Controller-Action methods and nowhere else.

ViewModel - is a simple model, which is a part of any composite ViewData models. One ViewModel can be included in various ViewData models. For example, NewsTeaserViewModel can be used on the home page(in HomeIndexViewData) or on the game page(in GameIndexViewDataBuilder) or somewhere else.

2 comments: