次の方法で共有



July 2009

Volume 24 Number 07

Extreme ASP.NET - Guiding Principles For Your ASP.NET MVC Applications

By Scott Allen | July 2009

Contents

Simplicity Through Separation
Trouble-Free Controllers
Controllers and Security
Plain Views
HTML Helpers
Strong Typing
Unobtrusive JavaScript
Partial Views
Views and Security
Big Models
Unit Tests

Although the release of Microsoft ASP.NET MVC 1.0 is relatively recent, many of the patterns and principles surrounding the framework have been around for a long time. The Model View Controller (MVC) pattern itself was passed down by Smalltalk developers from the 1970s, and is in use in a variety of frameworks and across a wide range of languages and diverse platforms. Thus, we can learn a lot about how to make the best use of the framework just by looking around our software ecosystem.

In this article, I want to lay out some principles you should follow when working with the ASP.NET MVC framework. These principles are not rules, but ideals and values you should cherish and keep in the forefront of your mind when building ASP.NET MVC applications that need to survive and evolve beyond a single release.

Simplicity Through Separation

When I was a young boy, I used to love jigsaw puzzles. It was a marvelous feeling to build simple scenes, like the picture of an apple, from a collection of complex and irregularly shaped puzzle pieces. This love of complexity is a virus I fight on a daily basis as a software developer.

As developers, we want to work in the opposite direction of the jigsaw puzzle. Instead of making the simple things complex, we should strive to make complex things simple. Many of the technologies around us are in place to help us move toward simplicity; we just have to make effective use of the technologies.

Let's take CSS and HTML as examples, because these are two technologies that every Web developer should be familiar with. If I want to draw a user's attention to required questions they failed to answer on my Web pages, then I might change the background color of the failed questions to red. I could do this by specifying the color red as a style on each failed question, or I could create a CSS rule specifying the color red and point any failed question elements to this style rule.

Although the CSS solution might require a little more work–because I write both CSS and HTML instead of just HTML–in the end it will prove itself a simpler solution. Using a CSS rule removes duplicate style information from my application, and I can easily change the color of all failed questions by modifying a single style rule. The CSS approach also allows me to create a rule whose name reveals its intended usage, e.g., failedValidation is a piece of code that gives me more information than reading #FF0000. These qualities of the CSS approach make the application easier to maintain and change, which is the simplicity we are striving to achieve.

Using CSS in combination with HTML exhibits another characteristic of simplicity, which is how CSS and HTML can have separate and distinct responsibilities. HTML becomes responsible for only the structure of a Web page, and CSS becomes responsible for the look of a Web page. This separation of responsibilities, or "separation of concerns," is an immensely powerful weapon in the battle against software complexity. Have you ever gone in to modify an object that represents a Web page only to find it is responsible for data access, data binding, business rule validation and coloring the background of a failed input field red? Was it easy to modify the object? Was it easy to focus on the one change request you had to implement inside this complex puzzle of logic?

The MVC design pattern has proved itself successful over time in part because the pattern forces a separation of responsibilities. This allows us to focus on specific tasks, hide our implementation details and make changes that don't interfere with other components. In MVC, for example, the view component is only responsible for presenting data. When inside the view, we can focus on presentation and not worry about where data originates. We can make changes to the view without worrying about data access code.

With the goal of simplicity through separation in mind, let's look at specific guidance on each of the three components in the model-view-controller pattern.

Trouble-Free Controllers

In an MVC pattern, controllers are in the middle of the action. Controllers handle incoming requests, interact with the model and select views for rendering. Because of their position, controllers can be difficult to implement while maintaining a separation of concerns. You have to remain diligent and not let your controllers become the centerpieces of your application logic.

As an example, I want to call out a contrast in two different versions of Oxite. Oxite is an open source content management system and blog engine built with the ASP.NET MVC framework and hosted on CodePlex at codeplex.com/oxite/. In an early version of Oxite (circa December 2008), the controller action responsible for saving a blog post was 103 lines of C# code. The action was responsible for validating the post, managing the relationship between a post and its tags, managing the relationship between a post and its parent, deciding if a post is new or an edited version of an existing post and saving the post to a repository. Contrast your mental image of this code with the current version, as shown in Figure 1.

Figure 1 Controller Action

[ActionName("ItemEdit"), AcceptVerbs(HttpVerbs.Post)] public virtual object SaveEdit(PostAddress postAddress, Post postInput) { Post post = postService.GetPost(postAddress); ValidationStateDictionary validationState; postService.EditPost(post, postInput, out validationState); if (!validationState.IsValid) { ModelState.AddModelErrors(validationState); return Edit(postAddress); } return Redirect(Url.Post(postInput)); }

It is obvious that the Oxite team has spent some time refactoring this controller action. All of the validation logic and relationship management hides behind an IPostService object, leaving the controller free to focus on its role as a mediator in the request. A good rule of thumb for a controller action is that once an action method exceeds 10 to 15 lines of code, you should consider refactoring to produce a simpler action method. You'll often find that the extra lines of code in the action represent business logic that would lead a happier life inside your business objects.

Controllers and Security

While our primary focus is on simplicity, I'd be amiss if I didn't introduce some important security-related principles for controllers. First, the controller must play a role in avoiding a Cross-site Request Forgery (CSRF). Phil Haack, program manager of the ASP.NET team, recently blogged on this topic in his article "Anatomy of a Cross-site Request Forgery Attack." He detailed how the controller and view can work together to avoid an attack. To summarize Phil's post: Whenever you have actions that authenticated users can invoke, then you should have the view render an antiforgery token to the client, and have the controller action validate this token by applying the ValidateAntiForgeryToken attribute. This solution is simple to implement, but you have to remember to put these safeguards in place.

Second, controllers should make use of the AcceptVerbs attribute to restrict certain actions to the operations of HTTP POST. A controller action that modifies data, like ultimately deleting an Order from the Orders table, should allow itself to execute only during an HTTP POST operation and never during an HTTP GET operation. With an HTTP GET operation, the parameters required for the action live in the URL. This means that a malicious user could send you an e-mail with an image, and point the source of the image to a URL that can delete records. You don't need to push any buttons. You only have to view the image to delete a record. Stephen Walther includes some more details on this scenario in his blog post, "Don't use Delete Links because they create Security Holes."

Plain Views

Views have a clear role when using the MVC design pattern on the Web. They present the model to the user. However, what does "present" mean in this context? Presentation could involve HTML, JavaScript, CSS, JSON, XML, and a heavy dose of server-side code. Views run the risk of becoming complicated with the many modes of presentation. Your job is to keep the views as simple as possible–not only because the view will be easier to maintain, but also because it is the hardest component to test. One of the biggest complexity risks in a view is to turn it into "tag soup."

"Tag soup" is a programmer's term for HTML that is difficult for the programmer to read and maintain, and possibly even difficult for the Web browser to parse. You look at the source code and it looks like soup–the chef stirred all the ingredients together into an illegible sea of broth.

Avoiding tag soup can be as easy as making sure the HTML in a Web form view is both well formed and well formatted. Both of these steps are easy to achieve in Microsoft Visual Studio, which highlights malformed HTML and gives you the option to format HTML (Ctrl K + Ctrl +D is the shortcut to format an entire document). However, there are additional challenges in Web form views, as demonstrated in the code below.

<% if ((bool)ViewData["isLoggedIn"]) { %> <img src="<%= ViewData["loggedInImage"] %>" /> <% } else {%> <img src="<%= ViewData["anonymousImage"] %>" onclick="login();" /> <% }%>

This code is an ugly amalgamation of HTML, C#, JavaScript and string literals. There are, however, a few guidelines we can follow to avoid such code.

HTML Helpers

HTML helpers are extension methods that you can use inside a view to encapsulate the creation of HTML and hide some simple presentation logic. In the above code, we have an IF condition that complicates the view. We also have image tags that are complicated by including server-side code inside the attributes. Rob Conery of Microsoft lives by a rule that he explains in his post, "ASP.NET MVC: Avoiding Tag Soup." His rule is, "If there's an IF, make a Helper." He then goes on to show the implementation of an HTML helper to build for a pager that displays links to navigate a paged list of items.

HTML helpers can also help you avoid the ugly intermixing of server-side code and HTML. Fortunately, the MVC framework includes a number of helpers. You can find more of them in the MVC Futures project and in the MVC Contrib project. Make sure to download both to look at the helpers available in these libraries. Some of them are specifically designed to work with strongly typed models, which is our next topic.

Strong Typing

You can put views into one of two categories: those that use the ViewData dictionary and those that use strong typing. Views that use the ViewData dictionary derive from System.Web.Mvc.ViewPage. Our tag soup example uses this approach. Strongly typed view pages derive from System.Web.Mvc.ViewPage<T>, where T is a generic parameter to specify the type of the model. I recommend you use strong typing.

Strong typing means your view knows exactly the type of model you expect it to work with. You don't have to guess at the magic string required to pull a particular piece of information from the ViewData dictionary. Instead, you can just use code like Model.Username and have IntelliSense aid you in the quest for data. In addition, by running the aspnet_compiler, you can catch any errors that you might have made when accessing the model. When you add a new view to an MVC project, the add view wizard makes it easy to select a strongly typed model, so you shouldn't have problems in the setup. In general, you'll find that removing magic string literals and relying on strongly typed views will help you maintain your software in the long run.

Unobtrusive JavaScript

We know that CSS and HTML can work together to separate the structure of a view from the visual presentation of the view in the browser. I strongly encourage you to use CSS as a tool to maintain this separation. However, modern Web pages have three primary concerns: structure, presentation and behavior. JavaScript implements this behavior. Including JavaScript in your view can be just as disruptive as including excessive amounts of style information and server-side code. Pages rich with JavaScript behavior demand a separation of structure from behavior so you can focus on the pieces independently.

Unobtrusive JavaScript is the practice of removing all signs of JavaScript from a view and placing any JavaScript you need into an external .js file. Unobtrusive JavaScript means you do not have any JavaScript functions defined in the view, and you do not have any on-click attributes, including JavaScript code in your HTML. Libraries like jQuery and Microsoft's ASP.NET AJAX libraries make it easy to reach into a page and wire up your events from the outside. All you need to do is add a <link> tag in your view to include the external .js file with your view behaviors.

As an example, the following jQuery code will run after the view renders on the client to wire up the click event of an element with an ID of "loginImage" to a JavaScript function by the name of "login." No on-click attribute required!

$(function() { $("#loginImage").click(login); }

Partial Views

Another technique you can use to manage the complexity of views is partial views. Partial views in the Web forms view engine are user control files with a .ascx extension. Just like in ASP.NET Web forms, we can use partial views to encapsulate HTML and code that we might want to reuse across multiple views (like a login display). We can also use partials to break down a complicated view into smaller pieces.

We can strongly type partial views by deriving from System.Web.Mvc.ViewUserControl<T>. The add new view wizard in an MVC application allows you to select a checkbox to choose between a view and a partial view, and also allows you to select the strongly typed model (see Figure 2). There is also an HTML helper available (RenderPartial) in the framework to make the job of using a partial view easy:

<% Html.RenderPartial("_LoginStatus"); %> Views and Logic

Figure 2

Figure 2 Add View Wizard

To paraphrase Albert Einstein, views should be as simple as possible, but not simpler. Views that contain any business logic or data access logic violate the spirit of the MVC design pattern and the principle of least surprise. No one familiar with the MVC pattern will expect to find business logic lurking inside a view.

Although the Web forms view engine allows you to create an associated code-behind file for a view, doing so will reduce any benefits you might have otherwise obtained by using MVC. Code-behind can only encourage the practice of putting more logic into a view and, even worse, can introduce the traditional ASP.NET page life-cycle events to a view. Page life-cycle events are one feature of ASP.NET that the MVC framework tries very hard to hide.

Views and Security

One word on security for views: Make sure you protect your application from cross-site scripting attacks (XSS) by HTML encoding output in a view. XSS attacks are enormously popular these days because they are relatively easy to execute. Although not all output technically needs encoding, I suggest you err on the side of safety and encode everything by default. Fortunately, there is already an HTML helper available for HTML encoding:

<%= Html.Encode(Model.Message) %>

In addition, ASP.NET will validate incoming requests by default. This request validation feature will look for requests containing un-encoded HTML input. If ASP.NET finds such a request, it will throw an exception before the request reaches a controller. In some cases, you may find you need to turn this feature off so you can accept HTML from the user. You can turn it off on a controller action by using the ValidateRequest attribute, but be careful with the input that arrives. The people behind XSS attacks have become exceptionally clever at hiding malicious input inside of requests.

Big Models

At last we've reached a place where we aren't building simple pieces of software, right? Doesn't the model represent our business logic with rich behaviors and complex rules?

It depends.

Technically, the model that the controller hands over to a view doesn't need any behavior or rules, because the view shouldn't be using any of these rules or behaviors in the model, if they exist. The view only wants to pick out data from the model to present to the user. Thus, even though you can use your business objects as the models for your views, many developers follow the guidance of creating view-specific models. We also call these types of models ViewModels.

ViewModels are simple data transfer objects–they contain all state but no behavior of any significance. In other words, you'll implement lots of properties on your ViewModels but no methods. ViewModels have the pleasant effect of giving a view exactly the data that it needs to present–no more and no less. While you optimize the design of your business objects for your business logic, you optimize the design of your ViewModels for your views.

Even when you do want to consume business objects from your view, many times you will struggle to introduce functionality that your business layer doesn't require. For example, suppose you need to list the patients in a hospital and include some aggregate calculations on their length of stay, such as minimums, maximums, averages and standard deviations. If your business logic never makes use of this information, you'll find it only clutters your logic with complexity. Remember, our goal is simplicity through separation.

ViewModels are the ideal abstraction to separate and isolate the model that your view requires from the model that your business requires. ViewModels introduce some additional work into your project, because you'll need to define additional classes to represent the ViewModels and map information into the ViewModel properties. However, this additional work can reap tremendous benefits because you are free to change your business logic without breaking the presentation layer, and vice versa. Also, you don't necessarily need a ViewModel class for each view. It's quite common to share a ViewModel class among related views.

Unit Tests

In this article, we've stressed simplicity. The MVC design pattern can guide us toward simplicity, but we still need to follow some principles. We also need to keep an eye out for places where complexity is creeping into our software. A good technique to discover complexity is to write unit tests. Unit tests not only help you maintain the quality of your software, but can also help you spot problems in a design (see Jeremy Miller's article on designing for testability "Design For Testability").

Perhaps you've been reading this article as a longtime Web forms programmer who never felt comfortable writing unit tests. It's possible you found unit tests too difficult to implement in the ASP.NET Web forms environment. This is your chance to start! The ASP.NET MVC team designed the framework with testability in mind. Start simple and let your unit testing experience grow. Eventually, you should find that unit tests are yet another weapon in the battle against the ever-growing complexity of software.

Send your questions and comments to xtrmasp@microsoft.com.

K. Scott Allen is a member of the Pluralsight technical staff and founder of OdeToCode. You can reach Scott at scott@OdeToCode.com or read his blog at odetocode.com/blogs/scott.