December 2007 - Posts

ASP.NET MVC Design Philosophy

This week the first preview of the ASP.NET MVC framework was released to the web as part of the ASP.NET 3.5 Extensions CTP Preview. It's been a few months since we started coding and as the lead developer on the project I'd like to share my thoughts on the design of this framework. This isn't a post about why MVC is great. Instead, it's a post about what we did to make MVC happen in ASP.NET.

The MVC Snake

One of the design strategies we used in the MVC framework is to have a clear sequence of stages in each request. The stages are reminiscent of the ASP.NET Page and Controls lifecycle, but much, much simpler. Here's a rough diagram of the stages from the slides that Scott Hanselman and I showed at the DevConnections conference.

MVC_Snake

When a request comes in, it goes through the stages as shown in the diagram. There are a few more stages than are shown, but they were removed so the "MVC Snake" diagram wouldn't get too cluttered. An important aspect of this diagram is that each stage only depends on the previous stages. A given stage typically doesn't make any assumptions about what follows it.

For example, the IController interface doesn't assume anything about views or any particular view factory implementation. However, views can assume that an IController was involved in their creation. The implication of this is that you can stray from our path at almost any stage and still end up with success. Want to implement a controller in some new, unique way? Go ahead!

URL Routing - Not part of MVC

Part of the MVC framework is a new URL routing mechanism. This allows you to create "friendly" and "pretty" URLs such as "http://example.org/store/categories" that map to arbitrary controller actions. There's no reason that the URL should specify a physical resource. The visitors to your site surely don't care how you architected your web site or web application.

If you've downloaded the CTP you'll notice that the URL Routing types sit in the System.Web.Mvc namespace. However, the URL Routing types aren't even a part of the MVC framework. URL Routing works with arbitrary IHttpHandlers so it can serve up arbitrary requests - not just MVC requests. For the current CTP because we just couldn't find a better place so we left the types in that namespace.

New Language Features

We're technology addicts, and it shows. The latest version of the C# language includes several new features and we decided to take advantage of them in the MVC framework. We used anonymous types in place of dictionaries, extension methods for helper methods, and lambda expressions as strongly-typed URLs.

But - and there is almost always a "but" - we went a bit too far in the current CTP. We've heard from many of you already that using anonymous types as dictionaries is cool, but hard to understand. And it doesn't have Intellisense. For the next release we're still planning on keeping those features, but we want to add more familiar ways of doing the same things. For example, we're going to let you use dictionaries when we want dictionaries. It seems obvious now. :)

Extensible, Extensible, Extensible

To build a framework that was extensible, testable, and functional, we had many important design decisions to make.

Perhaps most importantly, we went with an interface-based design. We didn't use interfaces for everything, but we did for most things. Typically we used interfaces for all behavioral types, and classes for data objects. The reason is that behavior needs to be mocked, whereas data is just data. Your data objects would probably look exactly the same as our implementations anyway, so we didn't want to bother with interfaces. There are always exceptions, and in the first CTP we may have made some decisions that don't agree with this, but we already have plans to address these for the next CTP.

Traditionally in ASP.NET, a lot of extensibility is enabled by inheriting from our classes and overriding virtual members. While it is a valid approach, it requires that you inherit from our components even if you just want to tweak one small behavior. In the MVC framework we still have many instances of that, but we also allow you to extend our functionality using composition. For example, to modify the behavior of a controller you can either derive from it or you can use a filter to externally change its behavior. The same exception I mentioned in the previous paragraph applies here.

Composition often yields better separation of concerns and allows you to more easily build cross-cutting concerns. For example, if you want to apply the same "behavior modification" to several different controller classes, you don't have to override the same virtual members in each one. You can just apply an external cross-cutting concern to the controllers you want. Thus, composition is more easily used with dependency injection frameworks.

What Does All This Lead To?

Well, what does this all lead to? Several things, of course. For example, each of the components in the MVC framework is fairly small and self contained, with single responsibilities. This means that due to their small size you have building blocks that are easier to understand. It also means that you can replace or even alter the building blocks if they don't suit your needs.

Having great amounts of extensibility means you can write unit tests for your application more easily. I remember the days when I didn't write unit tests. Dark days, they were.

But who is going to use the MVC framework? Not everyone wants to write unit tests or alter so-called building blocks. Being able to drag & drop a GridView and a SqlDataSource onto a WebForm is a valid scenario and it will continue to work. We're trying to create a better fit for ASP.NET for certain types of individuals.

Anecdotes and Miscellany

And to end with some fun facts and stories, the following is all true.

  • For the released CTP, the unit test code coverage numbers were about 93%, far more than any other major feature area. This does not include the code coverage by our QA guys, which I'm sure would bring the number up to at least 99%.
  • We had about 250 unit tests for MVC, and the ratio of unit test code to product code is about 1.9 to 1 (in terms of the size of the code files, not lines of code; I'm too lazy to do the latter!).
  • It feels good to unit test! I can't go back to the old ways.
  • Some of the code is written using true TDD, but some is code-first, but it is then quickly followed up with unit tests (prior to checking in).
  • Several weeks ago we invited several customers for a top secret sneak preview of the CTP, including the MVC framework. Jeffrey Palermo was attending and trying to build some samples with the MVC framework. Sure enough, he encountered some bugs in the product. The bits were still hot off the press and some of the bits hadn't quite settled in the right place. Millions of ones and zeros - what are the odds they'd all line up correctly on the first try? Anyway, I sat with Jeffrey for about half an hour and we managed to use many of the extensibility points in the framework to work around the bugs he encountered. The whole time I kept thinking (and probably saying aloud) that I was so glad I made those methods public or virtual or whatever! Without them Jeffrey would have been stuck.
  • The moral of the story: If you aren't sure, make it public. If we have to use a method, chances are, someone else does too.

MVC Resources

Downloads:

Documentation and discussion forums:

Blog posts and videos by team members:

Posted by Eilon with 39 comment(s)
Filed under: , , ,

How to Allow Generic Controls in ASP.NET Pages

Did you ever want to have a Repeater<Customer> control on your page? And its events were all strongly typed to recognize that each row was bound to a Customer object? Well, I came up with a way of doing it using some lesser known features of ASP.NET.

Today it's possible to use generic controls in ASP.NET, but you can't directly have a generic control in the tag markup. You'd typically have to do some trick such as deriving a concrete type from the generic type or instantiating the control from code-behind and adding it to a placeholder on the page.

My solution involves using a ControlBuilder to alter how the ASP.NET tag gets parsed. Typically ASP.NET parses a tag such as <cc:GenericControl runat="server" /> and instantiated a type called GenericControl in the mapped "cc" namespace. My ControlBuilder overrides that behavior by returning an instance of GenericControl<TDataItem> whenever that tag is encountered.

Here's an outline of my sample control:

namespace GenericControls {
    public class GenericControl<TDataItem> : Control where TDataItem : new() {
        public event EventHandler<GenericEventArgs<TDataItem>> Stuffing {
            add;
            remove;
        }
    }
}

Note that the control itself is generic over TDataItem, and that is exposes an event called "Stuffing" that uses a generic EventHandler with generic EventArgs also bound to TDataItem. So, if you have an instance of the GenericControl<Customer>, the event handler should take event arguments of type EventArgs<Customer>. Now you don't have to cast your event arguments to get to your data object!

To use the control you specify the generic argument in the form of a pseudo-property on the control tag itself called ObjectType:

        <gc:GenericControlGeneric runat="server" ID="CustomerGeneric"
            ObjectType="MyStuff.Customer, App_Code"
            OnStuffing="CustomerGeneric_Stuffing">
        </gc:GenericControlGeneric>

Yes, my naming convention is horrible. Unless you thing GenericControlGeneric sounds good. But it doesn't sound good; it's horrible! :)

And before anyone asks, the topic of generic syntax in tags has been discussed at length, so I won't repeat that here.

In summary, I encourage you to check it out and see if it helps solve any problems you've had, hopefully without creating any new problems. This was just a pet project I did to see whether it was possible to create generic controls in markup.

And finally, a caveat:

While this seems to work great at runtime, I've encountered some weird problems in Visual Studio to support this. For example, when you try to add an event handler for the Stuffing event through the property grid you might get some weird behavior.

Download the project source code here: GenericControl.zip

Posted by Eilon with 20 comment(s)
Filed under: , ,
More Posts