TheChaseMan's Frenetic SoapBox

Always looking for better ways to do things...

Spelunking - ASP.NET MVC 3 Released

MVC 3 was released just recently and I'm starting to get some strong feelings about what looks ugly and what looks clean for an MVC app. First of all, the Razor view engine is very slick and looks much cleaners than MVC 2. However, you can pretty easily make an MVC app look ugly - which IMO is 90% of the gripe the anti-WebForms people complain about. I still really love WebForms for LOB applications, and they certainly promote RAD. The complaint is that code-behind files can end up with a lot of code. So, to avoid the same issues with MVC:

  • Use ViewModel classes and avoid ViewData like the plague
    MVC is supposed to support separation of concerns? So why let your EF objects bleed through to the view?
  • Keep your business logic in your model.
    If code-behinds are ugly in WebForms, business logic in your controllers are going to make your code look even worse in MVC.
  • In order to avoid that icky feeling I get with EF used directly in my MVC Web app, create a real model.
    So far what seems cleanest is to create your EDMX (if you are not using code-first) in a DataAccess library. Create a BusinessInterface layer which the UI references along with a common library to hold your DTOs, or in this case ViewModel classes.

To clarify I'll show some example code using the AdventureWorks database for the EDMX, and display a list of employees in a WebGrid with Ajax paging and sorting. The EDMX will be the only thing in the DataAccess layer so I will not show that code. Let's start with the DTOs:

namespace AdventureWorks.Common {
   
public class GridExampleEmployee {
       
public int EmployeeID { get; set; }
       
public int ContactID { get; set; }
       
public string FullName { get; set; }
       
public string LastName { get; set; }
       
public string FirstName { get; set; }
       
public string MiddleName { get; set; }
       
public string Title { get; set; }
    }
}

namespace AdventureWorks.Common {
   
public class GridExampleViewModel {
       
public int TotalRecords { get; set; }
        
public List<GridExampleEmployee> Employees { get; set; }
    }
}

My BusinessInterface method will simply LINQ query the EDMX object model in the DataAccess DLL and return results within a DTO defined above.

using System.Linq;
using AdventureWorks.Common;
using AdventureWorks.DataAccess;

namespace AdventureWorks.BusinessInterface {
   
public class GridExampleSystem {
       
public GridExampleViewModel GetGridExampleViewModel(int page, int records, string sort) {
           
var fnResult = new GridExampleViewModel();
           
var edmx = new AdventureWorksEntities();
           
var queryResults = from c in edmx.Contacts
                               
join emp in edmx.Employees
                               
on c.ContactID equals emp.ContactID
                               
select new GridExampleEmployee {
                                    EmployeeID = emp.EmployeeID,
                                    ContactID = c.ContactID,
                                    FullName = c.LastName +
", " + c.FirstName + " " + c.MiddleName,
                                    LastName = c.LastName,
                                    FirstName = c.FirstName,
                                    MiddleName = c.MiddleName,
                                    Title = emp.Title
                                
};

             fnResult.TotalRecords = queryResults.Count();
             
switch (sort) {
               
case "Title":
                    queryResults = queryResults.OrderBy(r => r.Title);
                   
break;
               
default:
                    queryResults = queryResults.OrderBy(r => r.LastName);
                   
break;
            }

            fnResult.Employees = queryResults.Skip(page * records).Take(records).ToList();
           
return fnResult;
        }
    }
}


Now my controller class doesn't get messed up with any logic that should really be in my model (calculations, extra data fields such as “FullName” in this case, data access logic, etc).

using System.Web.Mvc;
using AdventureWorks.BusinessInterface;
using AdventureWorks.Common;

namespace MvcDemo.Controllers {
   
public class GridExampleController : Controller {
       
public const int PageSize = 10;
        
       
public ActionResult Index(int? page, string sort) {
           
var sys = new GridExampleSystem();
           
var startPage = 0;
           
if (page.HasValue && page.Value > 0) {
                startPage = page.Value - 1;
            }
           
GridExampleViewModel list = sys.GetGridExampleViewModel(startPage, PageSize, sort);
            
return View(list);
        }
    }
}


And I'm ready to crank out my View:

@using AdventureWorks.Common
@
using ThisController = MvcDemo.Controllers.GridExampleController
@model GridExampleViewModel

@{ ViewBag.Title = "Index"; }

@{
   
var grid = new WebGrid(canPage: true, rowsPerPage: ThisController.PageSize, canSort: true, ajaxUpdateContainerId: "grid");
    grid.Bind(Model.Employees, rowCount: Model.TotalRecords, autoSortAndPage:
false);
    grid.Pager(
WebGridPagerModes.All);
    @grid.GetHtml(htmlAttributes:
new { id="grid" },
        columns: grid.Columns(
            grid.Column(format: (item) => Html.ActionLink(
"Edit", "Edit", new { EmployeeID = item.EmployeeID, ContactID = item.ContactID })),
            grid.Column(
"FullName"),
            grid.Column(
"Title")
        ));
}

By the way, I took the CSS from http://www.freecsstemplates.org/. Takes a few minutes to replace the generated default stuff Visual Studio gives you. :)

Just my $.02 so far. I plan on building on this example over time just for fun. If anyone is interested I'll be zipping up the code files...this example is available here. Enjoy!


Digg!

posted on Sunday, January 23, 2011 11:48 AM

Feedback

#  1/25/2011 8:40 AM Codebix.com

This post has been featured on Codebix.com. The place to find latest articles on programming. Click on the url to reach your post's page.