Practical ASP.NET
Viewing Views in ASP.NET MVC
Peter continues his look ASP.NET MVC for ASP.NET developers (using the brand new version of ASP.NET MVC) by linking a view to a controller.
As I said in the first article in this series (
ASP.NET MVC for the ASP.NET Programmer) the benefit of ASP.NET MVC that I like best is its support for Test Driven Development (TDD). In my last column (
Controlling Controllers in ASP.NET MVC), I walked through the process of building a router and a controller that would gather data from a URL and prepare it to be displayed in an HTML page. And, by the way, at the end of this series, I'll be showing how, in an ASP.NET MVC application, you carry out the tasks more common to ASP.NET applications: retrieving data from a form, responding to user activities in the browser (e.g. mouse clicks) and so on.
But sticking with the peculiarly ASP.NET MVC tasks, for now: It's easy to see how that controller's Actions -- which are really just methods that return an object that inherits from ActionResult -- could be tested in a TDD environment. It would be easy to create a test script that would call the controller's action, passing the kind of data I expect to get from the URL and then inspecting the ActionResult that's returned to see if I get the right thing. However, the point of TDD is to ensure success at runtime and the default view doesn't support that.
When using the MVC pattern, you're really looking for a view that is so spectacularly stupid it's impossible to imagine it failing, at least in the sense of having an error in processing code. A default view in ASP.NET MVC just contains HTML and requests for data passed to the view from the controller. The ViewData object provides the default mechanism for passing data from the controller to the view.
A view for a "Hello, <name of person>" application might look like this:
<html>
<head></head>
<body>
<h1>Helo, <%=this.ViewData["userName"]%><h1>
<body>
There's a lot that can go wrong here that isn't testable in the same sense that the controller is testable. I've spelled "Hello" wrong, for instance, and that's a problem that will only be caught by inspection, unfortunately. I might also be displaying the wrong data item from the ViewData collection -- perhaps I should be display the "customerName" rather than the userName. Again, that will only be caught by inspection.
Some of the potential errors can be caught at compile time, avoiding runtime errors. For instance, my HTML might be badly formed (did you notice that my h1 closing tag isn't a closing tag?). However, that will be caught at compile time by my development tool and flagged as an error so that I can fix it (ideally, before my boss or client sees it).
The problem with using ViewData to pass data from the controller to the view is that typical mistakes do not generate compile time errors. To put data into the ViewData in your controller you use code like this:
this.ViewData["UserName"] = "Peter Vogel";
I'm using C#, rather than my usual Visual Basic for this column, because C# is a case-sensitive language and I'm trying to make a point here. In this example, I've loaded the data with the key "UserName" (uppercase U, uppercase N) but, if you look back at my view code, I'm retrieving the data with the key "userName" (lowercase u, uppercase N). A mismatch between keys used in the view and the controller is a terrific opportunity for error. And, since the keys are just strings, this is not an error that will be caught at compile (unlike my malformed HTML): you'll only discover this problem at runtime. This is exactly the kind of problem that you don't find until the page comes up (or doesn't come up) in front of your client and/or boss.
Fortunately, ASP.NET MVC provides an alternative to the ViewData: custom objects. The default View inherits from System.Web.Mvc.ViewPage and it's this class that provides you with the ViewData. However, you can alter your page to inherit from the generic version of the ViewPage class and then specify the type of the generic. This eliminates the key-mismatch error.
In my case, I would want the view to inherit from a generic object that passes user data. My first step would be to create a MyUser object with properties for the data that I want to pass to the View:
Public Class MyUser
Public Property userName() As String
End Class
My view would then inherit from the generic version of ViewPage and specify this class:
<%@ Page Title=""
Language="VB"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage(of MyUser)" %>
Now, in my controller, I can load data into properties with the support of IntelliSense:
Dim us As New MyUser
MyUser.userName = "Peter Vogel"
And retrieve the same data in the View, again with IntelliSense support. The object passed to the View is available through the page's Model property:
<h1>Hello, <%=this.Model.userName%><h1>
Now this is very much in the TDD tradition of ensuring runtime success!
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.