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!