ASP.NET MVC 2
ASP.NET MVC 1.0 has only been out for a few months now and with the rapid release cycle that we've been used to you shouldn't be surprised to know that ASP.NET MVC 2 is already starting to take shape. Oh no, I can hear it already...
What? Are you kidding me? I just learned ASP.NET MVC 1.0? How can I keep up if they're releasing so fast?
Put the torch & pitchfork down and have a seat. Change is a good thing! You might choose to ignore ASP.NET MVC 2 until it has a final release, however you'll be missing out on one of the very best ways of providing feedback to shape the product. In this article we will build a simple application demonstrating some of the features available in MVC 2 Preview 1.
Getting the Bits
First things first, if you want to follow along, go download the code. You can get it from http://www.microsoft.com/downloads/details.aspx?FamilyID=d34f9eaa-fcbe-4e20-b2fd-a9a03de7d6dd&displaylang=en. When you run the installer you'll be given an extra project type for the MVC 2 Framework. And before you ask, yes this is safe to install alongside ASP.NET MVC 1.0.
When you create a new project you should see this:
Figure 1: Creating a new ASP.NET MVC 2 application
Our Sample Application
For the purposes of demonstration, let's use a sample application. We will build some screens for a Contact Manager. The simple model looks like Figure 2.
Figure 2: A simple model for a contact manager application
We will use a new feature of ASP.NET MVC to build some display & edit screens. This feature is called Templated Helpers.
A new feature that was introduced in ASP.NET MVC 2.0 Preview 1 is called Templated Helpers. This allows you to create display and edit templates for various types. The benefit of these helpers over your standard ones are that these have compile time checking, intellisense, and strong support for refactoring.
A single picture explains this well:
Figure 3: The template helpers give you IntelliSense
Each of the properties of our model object are available to us here.
Let's say we want to output the contact's name:
That's not very impressive, you say? Of course, strings are pretty straightforward, and you could just output them directly if you wanted. What about the
By default, this would render:
Figure 4: Templated Display Helpers help us render display forms in a strongly-typed fashion
That birth date display isn't very nice looking. For example, the full time probably isn't needed. Let's give date times a new template to render from.
To customize the display templates, you need to create a new folder within your controller's view folder (or in Views/Shared) called DisplayTemplates. Inside of this folder, create a new View User Control (.ascx) file called DateTime.ascx. Notice how the name of the Type of property and the name of the template are the same. This is yet another convention that you can follow to simplify your code.
Figure 5: Custom Templates are placed in a DisplayTemplates folder with the filename matching the type
Make your DateTime.ascx file look like this:
Normally you'd use
ViewUserControl<T> to avoid the ugly casting, however
T must be a reference type, and
DateTime is a value type.
Another option would be to create a custom view model class, and assign a
DateTime property to it.
DisplayFor(m => m.BirthDate) call will use the new template when it renders:
Figure 6: Customizing the DateTime template
Alternatively, you could provide an editable version of this:
Also throw in a touch of CSS in
Site.master to make it look good:
Now we have a decent looking form, and no strings were used! Figure 7 shows the result.
Figure 7: Using Templated Input Helpers to render input controls
Notice how we created the labels and textboxes using the lambda expression on the model. This is much less error-prone and also supports refactoring. The real power of these helpers is that they work on complex types too!
Recall that our
Contact class is composed of a name and two other complex types:
PhoneNumber (used two separate times).
Now we can write
<%= Html.EditorFor(m => m) %> (effectively asking for an editor for our entire model) to produce this:
Figure 8: Rendering an entire model with Html.EditorFor(…)
Now the layout might need some work, but that's a pretty quick way to get an edit form for your model, wouldn't you say? Notice how both the simple properties are converted into labels & text fields, and the complex ones do the same, however they are noted with labels as well (see
To customize these we can provide custom partials in the
EditorTemplates subfolder (either under
/views/shared). Let's try this out on our
First, we create the
EditorTemplates folder in the
/Views/Shared directory. Then we create a new partial view called
Figure 9: Adding a Custom Editor Template for our PhoneNumber class
The file ends up looking like Listing 1.
Listing 1: PhoneNumber.ascx
This template makes our phone number displays much more friendly, as you can see in Figure 10.
Figure 10: Our PhoneNumber template in action
It's easy to see how much time this can save you, especially if you have many edit screens. You could easily add validation to this or whatever you require.
If you wanted to customize the default template for individual fields, how could you do it?
You can create a partial called
Object.ascx and place it one of the template folders to customize basic layout.
A known limitation in Preview 1 is that if you try to render a model that contains an interface it will throw an exception. In Preview 2 this has been fixed and will use the standard object template to output the properties.
The astute reader will notice that we gave those phone number textboxes an id value (namely
Suffix). Having two of these templates on the same page will cause id collisions, right? If you view the generated HTML source you'll notice that the ids were actually prefixed by the name of the referencing object's property name. Thus we have
HomePhone_Prefix, and so on.
Figure 11: The generated ids are prefixed with the parent object's property name
You can use this to your advantage. If you wanted one of your controls have the same name as its parent property name, then you can leave the control id blank, and it will fill in the name for you.
Multiple templates for each type
What if you wanted a special template for some of your fields, but you didn't want to modify all of them render using the template named after their type?
You have 2 options here. You can give the property an attribute of
[UIHint("SomeCustomTemplate")] or you can call it out explicitly when you render the template, as in
Html.DisplayFor(m=>m.SomeProperty, "SomeTemplate"). The latter only works if you're calling
Html.EditorFor() directly on that property. Otherwise you'll have to use the attribute based version. We're not directly rendering the templates for the model properties, so we'll have to use the attribute.
Say we added a new property on our Contact model object:
We want to display this value on the edit form, but we don't want it to ignore the time like we had for the
BirthDate. In fact, we'd like to output a template that displays the time relative to the current time. This gives you a friendly notice that the record was added 5 minutes ago, 22 minutes ago, 5 hours ago, etc.
In order to get this property to render a template other than
DateTime.ascx, we must add the
[UIHint] attribute to our property.
The template is added to both
DisplayTemplates, since the property might be viewable in both display and edit views. We can write a simple helper to do the relative date logic for us, leaving our template as simple as Listing 2.
Listing 2: RelativeDateTime.ascx
Again we can't use
DateTime is a value type. The helper we used is defined in Listing 3.
Listing 3: DateHelper.cs
Rendering the form now shows us the new template in use:
Figure 12: Our RelativeDateTime template now renders for this field
Customizing the Labels
The labels that are generated for us match the property names, but they aren't very human friendly. We can use the
[Display] attribute to control this, for example:
You can decorate your view model objects with additional attributes from the
System.ComponentModel.DataAnnotations namespace. For example, to make a field required:
There are also other attributes you can use, such as:
In Preview 1, this will drive server-side
ModelState validation. When Preview 2 drops, this will drive client-side validation with jQuery automatically off of the attributes on your model. Cool!
Figure 13: Adding [Required] to your properties drives server-side ModelState validation (in Preview 1)
Our contact class has an
Id property, but it's not something you would generally allow people to set. To tell the helper not to generate a field for a property, you can add another attribute:
Id won't be part of our form. Be careful though, in order to continue to use this in an edit form, you'll need to output the
Id manually as a hidden field so that it will be picked up in the form post.
A note about putting view attributes on your model
For simple applications, what you have seen here will probably work just fine. However in larger, more complex applications you'll want to separate the view concerns from your model. This is generally referred to as View Model, or Presentation Model, where you have an object specifically geared for displaying on a form or collecting data from the user. Your domain objects can be translated into these view model objects (and vice versa). Doing so will keep you from having to sprinkle view related attributes all over your domain model.
These templates won't be able to satisfy your every need when it comes to rendering edit forms, but it does get you on the ground and running quickly. When the default behavior doesn't work, you can always customize the templates for a given type.
One feature that was often requested in ASP.NET MVC 1.0 was support for Areas. Having multiple areas allows you to completely segregate your site into multiple sections, each having their own controllers, views, routes, and models. There were a handful of solutions for adding this behavior to 1.0, but each had their quirks.
As of Preview 1 each area is a separate project. In Preview 2, you'll be able to separate areas by using folders within the same project.
To implement multiple areas, you'll first need a root project to be the entry point. We'll also create two additional projects to serve as the sub-areas of our application. I placed these projects in a solution folder called Areas.
In the sub area projects, you'll need to remove all of the controllers, all of the views (except the
Views/web.config file, that is important). You can also safely remove the
Scripts folders. Figure 14 shows what it should look like.
Figure 14: Sub-area projects are normal ASP.NET MVC 2 projects, however some of their folders need to be removed
Notice that I've created a class called
Routes in each of the sub-area projects. This is simply a static class that we can use to register routes for that area. We'll look at those in a minute. First, we have to take a manual step to get this all working together.
To get this multi-project setup to work in Preview 1, we'll need to do some manual surgery on the project files. Right click on the Billing sub-area project and select Unload Project (Figure 15). Then right click on the same node and select Edit Billing.csproj (Figure 16). Inside of the project file you'll see a section that is commented out that looks like Listing 4.
Figure 15: Unloading the project so that we can edit the file manually
Figure 16: Select this to edit the raw XML of the project file
Listing 4: Modifying the project files to enable areas support
Notice that I've uncommented the bolded lines, just as the comments indicate (as this is an area child project). We'll do the same thing for the Shipping project. For the root project, (InternalOps) we will follow the same process, but we'll uncomment the other section instead, indicating that it is the root or "area parent project".
Go ahead and Reload the projects (right-click, Reload Project). You'll get a warning that the file might be "dangerous" but don't worry, just click OK. This dialog will hopefully go away in future preview releases.
Wait a Minute!
Do we really have to go through all of this junk any time we want a solution with multiple area projects? For Preview 1, yes. Future releases will provide much easier Visual Studio tooling support that should make this process painless.
Now, back to the projects. Did you notice that each area child project had a
Route.cs file? This needs to be created manually. It's just a static class that we can use to register our routes for each area. This helps keep the area registration contained to the project for which it is intended. You could choose to ignore this step if you want to provide all of your routes in the root project, however this is much cleaner.
Here is our billing
Routes.cs is shown in Listing 5.
Listing 5: Billing/Routes.cs
There is a new method called
MapAreaRoute that we use to register routes with an area. The method signature looks like this:
We have to tell the route what area this is for, and provide it a unique name (note that names have to be unique across the board, hence why I prefixed this default route name with the area name). We also have to indicate in which namespace our controllers reside.
Routes.cs is similar, but here we have a custom route (Listing 6).
Listing 6: Shipping/Routes.cs
Most of the area routes will start with a static identifier to select the area. It doesn't have to be the area name, but in most cases it will be. You will still have to look at your routes holistically, in order to determine which route matches first for a given request. Remember that order matters!
In our root project's
Global.asax (Listing 7) we'll call on both of these classes to register their routes.
Listing 7: Global.asax
We're still manually calling our area routes here, but we have control in what order they get registered. We also have to give the root area a (non-empty) area name, so I called it main here. The reason is because we'll be generating links between the areas, and we will have to specify what area the link is intended for.
Now let's add some controllers & views, shall we? I'd like to have the main page link to some recent orders (which will come from the Billing area). Each order will have a tracking number that will link to the Shipping area to track.
Here's the link on our master page to the Recent Orders action:
You have to specify what area you're pointing to in a master page like this. The reason is because you might be sitting in a different area when the link is generated. If you omitted the
Area="main" on the "Home" link, then it would try to access
HomeController from the current area, which might be Billing or Shipping. To prevent this, we must specify the area explicitly.
Figure 17: The link generates the appropriate URL for the Billing area
See how the URL is correctly generated (using our route definition from
Billing/Routes.cs) to the Billing area. Let's write a quick controller/action to show us this data:
Listing 8: Billing/Controllers/OrderController.cs
Here we're just faking the orders, but they could come from anywhere. Here is our view:
Listing 9: Billing/Views/Order/Index.aspx
Notice the link we generate for the tracking number. We specify the area name as "Shipping". This will use our custom route that we defined in the Shipping project. Our final page looks like Figure 18.
Figure 18: The orders index view links to the shipping area.
Our Track Package link is successfully using the custom route we created in
Shipping/Routes.cs. Next, let's create a quick & dirty controller/action & view for this link.
Listing 10: Shipping/Controllers/ParcelController.cs
Again, we're faking the data here. Our view....
Listing 11: Shipping/Views/Parcel/Track.aspx
And the final result:
Figure 19: The tracking action is invoked from the Tracking area
And that wraps up how to implement areas using multiple projects in MVC 2. There are some rough edges, but having built-in support for areas is great, and enables larger applications to segregate out various pieces of semi-related functionality into their own place.
Many folks questioned the need for multiple projects, as it does add quite a bit of complexity to the solution. For example, any view changes for sub-areas are compiled and included into the root project's template. As a result, if you change a sub-area view, you have to recompile in order to see it take effect on your site. Luckily, Preview 2 will give us support for single-project areas.
In this article, you learned about two big features of the upcoming ASP.NET MVC 2 framework. You learned about Templated Helpers to aid in building quick scaffolding for your view model objects. You also learned how to leverage the new Areas support with multiple projects to build larger applications with more separation of responsibility. This article covered Preview 1, however Preview 2 is due to be out very soon and will come with it many enhancements and improvements on what you see here. The best way to keep up and help shape the product is to participate in these early previews releases.
Ben Scheirman is a software developer specializing in .NET. He has worked extensively on the web on various platforms and languages. Ben is a Microsoft MVP, Microsoft ASP Insider, and Certified ScrumMaster. When not programming, Ben enjoys speaking, blogging, spending time with his wife and five won...
This author has published 4 articles on DotNetSlackers. View other articles or the complete profile here.
Please login to rate or to leave a comment.