I've been working on new Videos for ASP.NET MVC and the Mix conference, and I wanted to read some code that folks have created using the December CTP of ASP.NET MVC. There's a lot of good changes coming in the next Preview release and that feedback comes from active community members, so it's always good to read real source of real apps to see what folks are doing.
And so, Dear Reader, I present to you seventeenth in a infinite number of posts of "The Weekly Source Code." Here's some source I was reading this week.
This application is a free open-source Code Camp management application, so if you're thinking of running a Code Camp, start here. As of this writing, it's build on the December drop of ASP.NET MVC and also uses StructureMap, IESI Collections, MVC Contrib, log4net, RhinoMocks, NUnit and NHibernate. Aaron Lerch and others have been working on this app for the Indy Code Camp, so if you're around Indianapolis check them out.
Better Things
One thing that's fun to see when reading code is when folks name classes something like "BetterMousetrap," where the class name advertises that it's superior to something specific. This project has "BetterMvcHandler:"
public class BetterMvcHandler : MvcHandler
{
protected override void ProcessRequest(IHttpContext httpContext)
{
try
{
base.ProcessRequest(httpContext);
}
catch (Exception ex)
{
throw new Exception("Route used:" + RequestContext.RouteData.Route.Url, ex);
}
}
}
All they are doing is catching all exceptions, showing the current URL via the route, and pushing the original exception down a level. Smells a little iffy to me, but the valuable thing for the MVC Team to notice about this is that people need more insight into what is happening with Routes while debugging.
Here they use RhinoMocks so they can test their controllers without firing up ASP.NET/IIS. This will change some in the next drop, but this is totally the right idea. I've got a full 20 minute video coming showing just how to do this very thing:
namespace CodeCampServer.UnitTests
{
public static class Extensions
{
public static IHttpContext DynamicIHttpContext(this MockRepository mocks, string url)
{
IHttpContext context = mocks.DynamicMock();
IHttpRequest request = mocks.DynamicMock();
IHttpResponse response = mocks.DynamicMock();
SetupResult.For(context.Request).Return(request);
SetupResult.For(context.Response).Return(response);
SetupResult.For(request.AppRelativeCurrentExecutionFilePath).Return(url);
SetupResult.For(request.PathInfo).Return(string.Empty);
return context;
}
}
}
Law of Demeter
When creating lots of Domain Objects, like in this application, on of the things that comes up a lot is the validity (or not) of the Law of Demeter. Basically this law is a design guideline that says your object should avoid reaching deeply into other objects.
For example, in this application, there's an Attendee object. It has a Contact object, which has things like FirstName, LastName, Email, etc. If you see something like this:
[Test]
public void ShouldGetProperName()
{
Attendee attendee = new Attendee();
attendee.Contact.FirstName = "Homey";
attendee.Contact.LastName = "Simpsoy";
Assert.That(attendee.GetName(), Is.EqualTo("Homey Simpsoy"));
}
You might want to ask yourself if reaching "two steps" in in order to get the FirstName and LastName is a good idea as now you're coupling the caller not only to Attendee, but also Contact, not to mention you're assuming that Contact isn't null. If you follow this rule religiously (and I'm not saying you should) the downside is that you end up with a bunch of wrapper methods like GetFirstName, etc. on your top level object. It's something to think about. Perhaps it's less valid in the context of a Domain Model where all these objects find themselves intertwined, but if you find yourself writing a.b().c().d.something, your sense of code smell really should kick in.
Helper Things
Another thing to look for when reading code that uses a framework you've worked on is methods or classes named "Helper." This is another clue that maybe your framework isn't meeting the downstream developer's needs, especially if they have a large number of helpers to get-around issues with your framework.
public static class LinkHelpers
{
public static bool IsActive(this ViewPage thePage, string controller, string action)
{
return thePage.ViewContext.RouteData.Values["controller"].ToString() == controller &&
thePage.ViewContext.RouteData.Values["action"].ToString() == action;
}
public static bool IsActive(this ViewMasterPage thePage, string controller, string action)
{
return thePage.ViewContext.RouteData.Values["controller"].ToString() == controller &&
thePage.ViewContext.RouteData.Values["action"].ToString() == action;
}
...
There are a number of extension methods in this project where the developers have spot-welded new methods onto ViewPage and ViewMasterPage as mix-ins or extension methods. Interestingly though, they never ended up using the Helpers! I usually create helpers after things really start to hurt and I've moved into the Refactoring stage. It's usually a good idea to get a holistic perspective on the project first, rather than creating a number of Helpers and associated overloads in anticipation of future pain.
Again, I'm not picking on this project, rather just using it as a jumping off point for discussion. It's certainly easy to read code and throw stones. As Linus likes to say, "talk is cheap, show me the code."
There's a good writeup on how Kazi Manzur Rashid and Sonu wrote this Digg Clone. They are doing a couple of interesting things in their application.
JSON and Ajax
They are using the new DataContractJsonSerializer that was added in .NET 3.5 and lives in System.Runtime.Serialization.Json and lets you serialize your objects to and from Javascript Object Notation (JSON). They created a json.aspx file that takes the current ViewData and serializes it to JSON:
Response.Clear();
Response.ContentType = "application/json";
Response.Write(ViewData.ToJson());
Simple and it works. Whenever a controller wants to make JSON, they just ReviewView("Json", result). Personally, I would have made a JsonViewEngine and had the controller set its ViewEngine when neeeded, but suppose that would have rendered (ahem) the first parameter of RenderView moot.
They make good use of LINQ. Reading a nice clean LINQ query always makes me smile.
public TagItem[] GetTags(int top)
{
return Tags.Select (
t => new TagItem {
ID = t.ID,
Name = t.Name,
Count = t.StoryTags.Count()
}
)
.OrderByDescending(t => t.Count)
.ThenBy(t => t.Name)
.Take(top)
.ToArray();
}
Complex Views and ViewData
The main pages are fairly complex and include data from a number of different places, listing stories, tag clouds and categories. There's also multiple forms (remember the days when you could have multiple forms) on a page, like for the search box as an example. They've created a abstract BaseViewData class that others derive from, adding extra bits of data for their specific view.
For example, the Story Detail Page adds to the standard ViewData:
public class StoryDetailData : BaseViewData
{
public StoryDetailItem Story
{
get;
set;
}
}
And then the StoryController has this clever method that gets ViewData only if the type T is a BaseViewData and has a public parameterless constructor (that's the new() at the end):
private T GetViewData<T>() where T : BaseViewData, new()
{
T viewData = new T();
viewData.IsAuthenticated = IsUserAuthenticated;
viewData.UserName = CurrentUserName;
viewData.Categories = DataContext.GetCategories();
viewData.Tags = DataContext.GetTags(_topTags);
return viewData;
}
It populates the methods shared by the base class and returns the derived type. I wouldn't have come up with this on my own without some thought.
Each of these projects have different styles but each is a good place to start looking when writing a new ASP.NET MVC Application.
Related Posts and Links to Read
Hosting By