Scott Hanselman

The Weekly Source Code 17 - ASP.NET MVC Community Code Edition

February 27, '08 Comments [15] Posted in ASP.NET | ASP.NET MVC | Source Code
Sponsored By

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.


CodeCampServer (source)

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."


Kigg - Digg clone in ASP.NET MVC (Live Demo)

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

Technorati Tags: ,,

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Wednesday, February 27, 2008 1:48:53 AM UTC
Thanks Scott!

BTW, here's a friendly public reminder that CodeCampServer is under active development and is still in a fair bit of "flux" -- hence things like unused helpers, etc.
Wednesday, February 27, 2008 3:42:09 AM UTC
Oh, and the IndyCodeCamp.com site isn't using CodeCampServer yet - it wasn't ready (sucks) so we went with dotnetnuke.
Wednesday, February 27, 2008 8:26:08 AM UTC
I particularly like the Law of Demeter explanation.

It reminds me of a pattern I read in Martin Fowler's Refactoring book. He suggests an alternative of moving properties up into the object you need it in, which isn't always suitable.

Something good to think about.

Thanks.
Richard
Wednesday, February 27, 2008 2:52:25 PM UTC
Hi Scott,
am I wrong or the MVC Contrib link isn't correct?

BTW, thank you for the post.
Fabrizio
Wednesday, February 27, 2008 3:18:27 PM UTC
I have a question about the GetViewData example. Would the declaration actually be:

private T GetViewData<T>() where T : BaseViewData, new()...

or do I not understand generics?

Thanks.
Greg McCarty
Wednesday, February 27, 2008 3:39:53 PM UTC
I have seen the Law of Demeter quoted several times recently, but I have never understood how it became a "Law" in the first place. It seems to me that it just creates code bloat for no significant benefit.

P.S. In "Better Things", I think you meant "people want more insight into what is happening..."
Wednesday, February 27, 2008 3:42:29 PM UTC
I must confess, I wrote the BetterMvcHandler. I definitely think this belongs in the framework, but if it isn't I think I will rename it to DebuggingMvcHandler or something to emphasize that this is something that is helpful while debugging your routes, but after that's done it's unneeded complexity.

Either that, or maybe I'll name it UberMvcHandler... hrrmmm.
Wednesday, February 27, 2008 3:48:40 PM UTC
Hi Scott! I've been following the ASP.NET MVC project, but had not found any complete real-world examples, until now.

I really enjoy this kind of post, keep up the great work!

Thanks,
Mitch
Wednesday, February 27, 2008 6:07:37 PM UTC
Scott,

As always you rock! Thanks for the information!

As an evangelist you have gotten us all worked up about these tools, as a developer and architect I am trying to figure out a way to consume them.

Our team is looking for an example application that covers the following:

* MVC
* Entity Framework (With Validation) Hoping to see the Validation Application Block. We are hoping to have our validation exposed primarly via the model.
* Ajax and how it can be used in MVC and Unit testing
* Gridview and other controls that have built in behaviors? The current examples seem to be skipping the difficult issue of dealing with some of the more robust controls.
* Unit testing it all.
* As a bonus maybe throw in some dependency injection, maybe Unity! Oh and a free toaster!

Brad Abrams wrote an example that covers some of this. What we are struggling with is how to use the Validation Application Block with Entity Framework. We were hoping to separate our declaratively describe the most of our validation.

http://blogs.msdn.com/brada/archive/2008/01/29/asp-net-mvc-example-application-over-northwind-with-the-entity-framework.aspx

Thanks for the preview of some of the great tools!

Bryan Reynolds
Wednesday, February 27, 2008 7:46:49 PM UTC
Thanks Scott, for mentioning Kigg in your weekly source code series.

>>>>>>>>>>>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.

Certianly a view factory is better candidate, but as mentioned in my post we want make it simple and easy, I think it is better to let everybody understand the basic things first and then introducing those extention points. For a youngster, learning ASP.NET MVC is much easier than the Web Form model. From the DotNetSlackers.com, we will be publishing a series of articles on MVC which will cover from basic2advance.

>>>>>>>>>>>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.

What is the better idea when all of the views share some common data? Pls suggest.

@Greg McCarty:
It is obviously private T GetViewData<T>() where T : BaseViewData, new() and I am sure you can overlook this silly mistake.
Wednesday, February 27, 2008 8:04:17 PM UTC
@Greg - You're right, the <T> wasn't being escaped in the HTML source.

@Fabritzio - Fixed!

@Ben - It IS better if it adds value! I'll take it to PhilHa and see what the team thinks.

@Kazi - Good point about making it easier. As I mentioned, it's also a stylistic judgement, and it certainly works either way. Nice use of the word "youngster." :)

@Kazi - There isn't a better idea when the views share common data. When I say "I wouldn't have come up with this," I was saying it was exceedingly clean and clever and literally, I am not clever enough to have seen this pattern. My compliments to the chef.
Friday, February 29, 2008 9:53:44 AM UTC
Thanks Scott, for your appreciation.
Thursday, March 06, 2008 6:55:38 PM UTC
The thing that bothers me about the Demeter example is that the repeated access of attendee.Contact instead of putting the result in a local.

Of course with the new extension method support, you could easily add your own setup a Contact method (private to the view even!) that does the right thing:

private Contact Setup(this Contact contact, string first, string last)
{
contact.FirstName = first;
contact.LastName = last;
return contact;
}
Friday, March 07, 2008 5:23:19 PM UTC
Notice how my gravatar shows up on this comment, but not the first one? That's because the gravatar icon is stored under idisposable@gmail.com not IDisposable@gmail.com, you need toi lowercase in the gravatar provider (or someone there need to handle requests case-insensitive).
Friday, March 07, 2008 5:24:08 PM UTC
Scratch that, you HAVE to do it, since you send them the MD5...
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.