Chapter 13. What's New in ASP.NET MVC 2

ASP.NET MVC 2 builds on ASP.NET MVC 1.0, introducing productivity-oriented enhancements and features throughout the Framework. Although we've touched on them throughout the book, this chapter will summarize them for those of you who have used ASP.NET MVC 1.0 and want a recap on what's changed.

There's a white paper on the ASP.NET website that overviews the changes (www.asp.net/learn/whitepapers/what-is-new-in-aspnet-mvc/). We're going to focus on some of the standout features you'll really want to know about.

SECURITY

ASP.NET MVC 2 has several enhancements which help you to write secure web applications. We've talked about them in Chapter 9, but look at two in a bit more detail here: HTML Encoding Code Blocks and JsonResult.

HTML Encoding Code Blocks

HTML Encoding Code Blocks are actually a feature of ASP.NET 4, but they're so useful in ASP.NET MVC 2 that it's worth calling them out. This syntax is shorthand for Html.Encode (well, actually the HttpUtility.Encode that powers Html.Encode).

Instead of this:

<%= Html.Encode(Model.PotentialEvil) %>
Code snippet 13-1.txt

You can now type this:

<%: Model.PotentialEvil %>
Code snippet 13-2.txt

We hope that we convinced you of the importance of always HTML-encoding any external data you display during the discussion on XSS in Chapter 9. With this new HTML Encoding Code Block syntax, there's just no excuse for writing out un-encoded content — it doesn't require a single additional character of code!

There are cases in which you want to output text that contains HTML or has already been encoded. In those cases, you don't want to encode the string again, which would result in double-encoding. In order to allow you to always use the <%:%> syntax, ASP.NET 4 introduces an IHtmlString, which the Framework will view as already encoded.

There are two concrete implementations of IHtmlString that you should be aware of. ASP.NET 4 includes HtmlString, which can be easily created via its public constructor:

<%: new HtmlString("<acronym title="Model View Controller">MVC</acronym>") %>
Code snippet 13-3.txt

Additionally, ASP.NET MVC 2 includes a concrete implementation, MvcHtmlString. This is used by HTML Helper methods and works with the <%:%> syntax under ASP.NET 4 but is backward-compatible under ASP.NET 3.5.You can use the static MvcHtmlString.Create method to create a new MvcHtmlString that should not be encoded:

<%: MvcHtmlString.Create("<h1>Hello!</h1>") %>

So when should you use MvcHtmlString versus HtmlString? Well, if you're working on an ASP.NET MVC 2 site that only runs under ASP.NET 4, you can use either. If you are writing helpers or any kind of library code that may be used under ASP.NET 3.5, you should use MvcHtmlString because it is backward-compatible.

JsonResult

By default, JsonResult only responds to an HTTP-POST request to guard against a vulnerability. This can allow sensitive data sent via JSON to be exposed. This vulnerability requires that you are exposing a JSON service which:

  • Returns sensitive data.

  • Returns a JSON array.

  • Responds to GET requests.

  • Has JavaScript enabled on the browser making the request (very likely the case).

  • Has a browser making the request supporting the __defineSetter__ method.

In this case, an attacker can use malicious JavaScript to redefine the Object setter method before your service is called, which would allow them to call arbitrary JavaScript when the JSON data is rehydrated into JavaScript objects:

<html>
...
<body>
    <script type="text/javascript">
        Object.prototype.__defineSetter__('Id', function(obj){alert(obj);});
    </script>
    <script src="http://example.com/Home/AdminBalances"></script>
</body>
</html>
Code snippet 13-4.txt

Thus, if you never send sensitive data in JSON format or you only send JSON in response to a POST request, or the like, then your site is probably not vulnerable to this particular vulnerability (although there could be others).

While you could guard against this in ASP.NET MVC 1.0 using the [AcceptVerbs(HttpVerbs.Post)] to prevent GET access to your JSON services, it required action on your part. In ASP.NET MVC 2, this is taken care of for you — services that return a JsonResult will only respond to an HTTP-POST request.

If you need to support GET requests for JSON data that will not contain sensitive information, you can bypass this security measure using an overload for the Json() controller utility method, as shown below.

public ActionResult Index()
{
    var data = new {
        timestamp = DateTime.Now,
        message = "Hello!"
    };

    return Json(data, JsonRequestBehavior.AllowGet);
}
Code snippet 13-5.txt

PRODUCTIVITY

ASP.NET MVC 2 has several new features that allow you to increase your productivity. Strongly typed HTML Helpers eliminate strings from your View code, allowing you to perform type checking, refactoring and to use IntelliSense. Templated Helpers give you the flexibility of defining HTML for both edit and display of a specific data type. The new Validation system in ASP.NET MVC 2 allows you avoid hand-written, imperative validation logic with simple, declarative attributes, implement client side validation, and organize your large projects with Areas.

Strongly Typed HTML Helpers

ASP.NET MVC 2 includes overrides for many HTML Helpers that allow you to specify bindings using expressions rather than strings. For example, you can replace <%: Html.TextBox("PropertyName") %> with <%: Html.TextBoxFor(m => m.PropertyName) %>. This gives you a lot of benefits, including type checking, IntelliSense, and easier refactoring. See Figure 13-1.

FIGURE 13-1

Figure 13.1. FIGURE 13-1

The templates that create a new view use these strongly typed HTML helpers, too. For example, here's an Edit form for our NerdDinner model using the Create template:

<% using (Html.BeginForm()) {%>
  <%: Html.ValidationSummary(true) %>

  <fieldset>
    <legend>Fields</legend>

    <div class="editor-label">
      <%: Html.LabelFor(model => model.Title) %>
    </div>
    <div class="editor-field">
      <%: Html.TextBoxFor(model => model.Title) %>
      <%: Html.ValidationMessageFor(model => model.Title) %>
    </div>

    <div class="editor-label">
      <%: Html.LabelFor(model => model.EventDate) %>
    </div>
    <div class="editor-field">
      <%: Html.TextBoxFor(model => model.EventDate) %>
      <%: Html.ValidationMessageFor(model => model.EventDate) %>
    </div>

    <div class="editor-label">
      <%: Html.LabelFor(model => model.Description) %>
</div>
    <div class="editor-field">
      <%: Html.TextBoxFor(model => model.Description) %>
      <%: Html.ValidationMessageFor(model => model.Description) %>
    </div>

    <!- other fields removed for brevity ->

    <p>
      <input type="submit" value="Create" />
    </p>
  </fieldset>

<% } %>
Code snippet 13-6.txt

For more information on Strongly Typed HTML Helpers, see the "Using the HTML Helpers" section in Chapter 6.

Templated Helpers

Templated Helpers allow you to define the HTML that is shown for both edit and display of a specific data type. You can use them with both system data types (such as System.DateTime values) as well as your own data types (such as a User or Order class) that you've defined, which gives you a huge amount of flexibility.

Templated Helper Methods and Conventions

Templated Helpers are displayed using the Html.DisplayFor and Html.EditorFor helper methods. ASP.NET MVC 2 includes default display and edit templates for many common data types, as shown in Table 13-1.

Table 13.1. ASP.NET MVC 2 Default Templates

DEFAULT DISPLAY TEMPLATES

DEFAULT EDIT TEMPLATES

EmailAddress

HiddenInput

HiddenInput

MultilineText

Html

password

String

String

Url

Collection

Collection

Boolean

Boolean

Decimal

Decimal

String

String

Object

Object

 

ASP.NET MVC 2 uses a convention-driven system to allow you to substitute your own template files for any type, including the ones in Table 13-1. Here are the conventions you need to adhere to:

  • You can use either an ASPX or ASCX file with the same name as your data type. For example, a display template for the Enumeration type would either be named Enumeration.aspx or Enumeration.ascx.

  • Display templates must be inside a folder named DisplayTemplates, and Editor templates must be inside a folder named EditorTemplates.

  • Your template folder needs to be in the standard search path for the Web Forms View Engine.

This is pretty simple given an example. Again assuming that you are overriding the Enumeration display template and you decide to use an ASPX extension, your file could use any of the following:

  • ~/Areas/AreaName/Views/ControllerName/DisplayTemplates/Enumeration.aspx

  • ~/Areas/AreaName/Views/Shared/DisplayTemplates/Enumeration.aspx

  • ~/Views/ControllerName/DisplayTemplates/Enumeration.aspx

  • ~/Views/Shared/DisplayTemplates/Enumeration.aspx

The astute reader may have noticed that the default templates include some "data types" that don't exist, such as EmailAddress, HiddenInput, and MultilineText. You can specify these using the DataType attribute, and you can map properties to a specific template by name using the UIHint attribute.

[DataType(DataType.EmailAddress)]
public string CustomerEmail { get; set; }

[UIHint("SpecialEmailAddressTemplate")]
public string SpecialEmail { get; set; }

Example: Using a DateTime Editor Template

For this example, you'll create a custom editor template that will be shown for all DateTime edit fields in an application. You'll use the standard jQuery UI Datepicker plug-in, and you'll do it with as little code as possible. Follow these steps:

Note

We're just doing a Datepicker for this example to keep things simple, because there's a great jQueryUI plug-in for it. For a more complex sample that uses a DateTime picker, you can see the live NerdDinner source at http://nerddinner.codeplex.com.

  1. Download the jQueryUI Datepicker control. There are a variety of ways to reference this plug-in, such as using the Google CDN or building a custom jQueryUI download at http://jqueryui.com/download. If you just want the scripts without the accompanying theme files, you can get them from http://jquery-ui.googlecode.com/svn/trunk/ui/. You'll need the following dependencies:

    • jquery.ui.datepicker.js

    • jquery.ui.core.js

    • A theme, available for download from http://jqueryui.com/themeroller/

    Copy these files into your ~/Scripts directory, as shown in Figure 13-2, and add a reference to them in your Site.Master.

  2. Create the ~/Views/Shared/ EditorTemplates directory if it doesn't exist.

  3. Right-click the EditorTemplates directory and select "Add View." In the "Add View" dialog (Figure 13-3), add a partial view named DateTime that's strongly typed to System.DateTime.

    FIGURE 13-2

    Figure 13.2. FIGURE 13-2

    FIGURE 13-3

    Figure 13.3. FIGURE 13-3

  4. The created view just has one line of code that declares the control. Add the following code:

    <<%@ Control Language="C#"
        Inherits="System.Web.Mvc.ViewUserControl<System.DateTime>" %>
    <%: Html.TextBox("",
        ViewData.TemplateInfo.FormattedModelValue,
        new { @class = "text-box single-line" }) %>
    <script type="text/javascript">
        $(function () {
            $('#<%: ViewData.TemplateInfo.GetFullHtmlFieldId("") %>')
            .datepicker();
        });
    </script>
    Code snippet 13-7.txt

That's it! Now any DateTime edit fields in our site will show a calendar pop-up. To see this in action, you can create a Controller action that returns a model, which includes a System.DateTime property.

public ActionResult DateEntry()
{
    var party = new Party
    {
        Event = "Visual Studio 2010 Release",
        PartyDate = new DateTime(2010, 04, 12)
    };
    return View(party);
}
Code snippet 13-8.txt

Then you can create a strongly typed view of type System.DateTime and add a call to EditorForModel in the generated view.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
    Inherits="System.Web.Mvc.ViewPage<Planners.Models.Party>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <%: Html.EditorForModel()%>
</asp:Content>
Code snippet 13-9.txt

When this page is displayed and you click the textbox, you should see a Calendar pop-up. See Figure 13-4.

Model Validation

Model Validation is one of the biggest productivity boosters in ASP.NET MVC 2. It replaces tons of hand-written, imperative validation logic with simple, declarative attributes.

FIGURE 13-4

Figure 13.4. FIGURE 13-4

Defining Validation Rules with Data Annotations

Model Validation takes advantage of DataAnnotations, from the System.ComponentModel namespace. You can take advantage of the DataAnnotation attributes in Table 13-2.

Table 13.2. DataAnnotation Attributes used in Model Validation

NAME

USE

RequiredAttribute

Verifies that a property value is not null

StringLengthAttribute

Verifies that a string property's length is does not exceed the maximum length

RangeAttribute

Verifies that a property value for a numeric field is no less than the minimum value and no greater than the maximum value

RegularExpressionAttribute

Verifies that a property value matches the specified regular expression

Let's look at how this simplifies validation for the NerdDinner Dinners model. In the ASP.NET MVC 1.0 version, you had custom code that was called from the model's IsValid method.

if (String.IsNullOrEmpty(Title))
    yield return new RuleViolation("Title required","Title");

if (String.IsNullOrEmpty(Description))
    yield return new RuleViolation("Description required","Description");

if (String.IsNullOrEmpty(HostedBy))
    yield return new RuleViolation("HostedBy required", "HostedBy");

//etc.
Code snippet 13-10.txt

This code is completely repetitive and serves no purpose other than to specify that certain fields are required and the error messages to display if they fail validation. You can replace all of that code by just putting the RequiredAttribute on those properties in our model.

[Required(ErrorMessage = "Title is required")]
public string Title { get; set; }

[Required(ErrorMessage = "Description is required")]
public string Description { get; set; }

[Required(ErrorMessage = "HostedBy required")]
public string HostedBy { get; set; }
Code snippet 13-11.txt

You've replaced six lines of custom validation logic with three attributes, without losing anything (and as you'll see later, you've gained some new functionality).

Client-Side Validation

One of the great features of Model Validation is that it makes it incredibly easy to add client-side validation. How easy? One line of code. Add a call to Html.EnableClientValidation above your form, like this:

<%: Html.ValidationSummary() %>
<% Html.EnableClientValidation(); %>

<% using (Html.BeginForm()) { %>
Code snippet 13-12.txt

That's it. Now you get client-side validation that enforces whatever rules you've set on your Model via Data Annotations.

For example, let's create a simple model with some Data Annotations:

public class Product {
[Required(ErrorMessage = "Please supply a name")]
[RegularExpression(@"[A-za-zs]+", "Product name must be alphabetic")]
[DisplayName("Product Name")]
public string ProductName { get; set; }

[Required(ErrorMessage = "Please supply a name")]
[DisplayName("Unit Price")]
[Range(1.00, 1000.00)]
public decimal UnitPrice { get; set; }

[Required]
[DisplayName("Number in stock")]
[Range(0, int.MaxValue)]
public int UnitsInStock { get; set; }

public bool Discontinued { get; set; }
}
Code snippet 13-13.txt

Now you'll create an Edit form, enable the client validation, run the application, and click submit. See Figure 13-5.

FIGURE 13-5

Figure 13.5. FIGURE 13-5

All the validation is sent to the client when the page is requested, so there are no additional calls back to the server to validate that the code meets the requirements set via Data Annotations.

The client-side validation is completely extensible. The content sent to the client includes all the validation rules. Here's what you'd see for a very simple Registration page:

</form><script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata)
{ window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push(
{"Fields":[
{"FieldName":"username","ReplaceValidationMessageContents":true,
"ValidationMessageId":"username_validationMessage","ValidationRules":[]},
{"FieldName":"email","ReplaceValidationMessageContents":true,
"ValidationMessageId":"email_validationMessage","ValidationRules":[]},
{"FieldName":"password","ReplaceValidationMessageContents":true,
"ValidationMessageId":"password_validationMessage","ValidationRules":[]},
{"FieldName":"confirmPassword","ReplaceValidationMessageContents":true,
"ValidationMessageId":"confirmPassword_validationMessage",
"ValidationRules":[]}],
"FormId":"form0","ReplaceValidationSummary":false});
//]]>
</script>
Code snippet 13-14.txt

This means that you're free to replace the client-side validation code with whatever logic you'd like.

Model Metadata

In addition to defining your Model Validation rules using Annotations, you can make use of some additional annotations to define how your model properties are displayed and edited. These Attributes are described in Table 13-3.

Table 13.3. Attributes use in Templates

NAME

USE

HiddenInputAttribute

Applying this attribute will generate a hidden input when editing this model. By default, it will also hide all the surrounding HTML, unless you set the DisplayValue flag to be true; in this case, it will generate both a displayed value (with its surrounding HTML) and the hidden input. In addition to setting HideSurroundHtml, it also sets a TemplateHint of "HiddenInput" (which can be overridden with [UIHint]).

UIHintAttribute

This will set the TemplateHint property with the name of the UI hint. We first look for a PresentationLayer type of "MVC", and if there isn't one, look for an empty or null PresentationLayer.

DataTypeAttribute

This will set the DataTypeName property. You can use this provide custom validation logic that is associated with a data type that you define.

ReadOnlyAttribute

This will set the IsReadOnly property. Note that because we use Type descriptors, any property without a public setter will have the [ReadOnly] attribute automatically.

DisplayFormatAttribute

Setting NullDisplayText on this attribute sets NullDisplayText on model metadata. Setting DataFormatString will set DisplayFormatString on model metadata; if ApplyFormatInEditMode is set to true, then it will also set the EditFormatString on model metadata. Setting ConvertEmptyStringToNull on the attribute will set ConvertEmptyStringToNull on model metadata.

ScaffoldColumnAttribute

This will set both the ShowForDisplay and ShowForEdit properties.

DisplayNameAttribute

This will set the DisplayName property. You'll use this to override the property names (which often come from database columns with ugly names) so they look presentable in field labels and validation messages.

Html.Action and Html.RenderAction

ASP.NET MVC 2 introduces two powerful new HTML Helper methods, Action and RenderAction. These helpers are similar to Partial and RenderPartial, but are a lot more flexible. While Partial allows you some organization and reuse benefits, it's constraining since it only executes within the context of the parent View.

Action and RenderAction, on the other hand, execute a separate Controller action method and display the results inline. They have no dependency on the containing View for model data or other controller context, which opens up a lot of new scenarios that were previously impossible or impractical with Partial and RenderPartial.

See our discussion of Action and RenderAction in Chapter 6 for more information.

Code Snippets in Views

One cool new feature of VS10 is that snippets now work in the HTML editor. A list of snippets, with the code they generate for you, is shown in Table 13-4.

Table 13.4. Code Snippets in Views

NAME

SHORTCUT

GENERATED CODE

if statement (C#)

ifcs

<% if ($expression$) { %> $selected$$end$ <% } %>

if statement (VB)

ifvb

<% If $condition$ Then %> $selected$$end$ <% End If %>

if else statement (C#)

ifelsecs

<% if ($expression$) { %> $selected$$end$ <% } else { %>

<% } %>

if else statement (VB)

ifelsevb

<% If $condition$ Then %> $selected$$end$ <% Else %>

<% End If %>

foreach statement (C#)

foreachcs

<% foreach ($type$ $identifier$ in $collection$) { %> $selected$$end$ <% } %>

foreach statement (VB)

foreachvb

<% For Each $Variable$ As $ObjectType$ In $CollectionObject$ %> $selected$$end$ <% Next %>

action link helper

actionlink

<%= Html.ActionLink("$linktext$", "$actionname$") %>$end$

textbox helper

textboxmvc

<%= Html.TextBox("$name$") %>$end$

partial view rendering (C#)

renderpartialcs

<% Html.RenderPartial("$viewname$"); %>$end$

partial view rendering (VB)

renderpartialvb

<% Html.RenderPartial("$viewname$") %>$end$

form helper (C#)

beginformcs

<% using (Html.BeginForm()) { %> $selected$$end$ <% } %>

form helper (VB)

beginformvb

<% Using Html.BeginForm() %> $selected$$end$ <% End Using %>

AJAX-enabled form helper (C#)

beginformajaxcs

<% using (Ajax.BeginForm("$actionname$", new AjaxOptions {UpdateTargetId= "$elementid$" })) { %> $selected$$end$ <% } %>

AJAX-enabled form helper (VB)

beginformajaxvb

<% Using Ajax.BeginForm("$actionname$", new AjaxOptions {UpdateTargetId= "$elementid$" }) %> $selected$$end$ <% End Using %>

validation summary helper

validationsummarymvc

<%= Html.ValidationSummary() %>$end$

Organize Projects with Areas

Areas let you organize a large project into multiple smaller sections in order to manage the complexity of a large web application. Each section (area) typically represents a separate section of a large website and is used to group related sets of Controllers and Views.

To create a new area, do the following:

  1. In Solution Explorer, right-click the project, click Add, and then click Area, as shown in Figure 13-6.

  2. This displays a dialog box that prompts you for the area name. After you enter the area name, Visual Studio adds a new area to the project. Add a new area to our site called Admin. See Figure 13-7.

    FIGURE 13-6

    Figure 13.6. FIGURE 13-6

    FIGURE 13-7

    Figure 13.7. FIGURE 13-7

  3. This includes a new file called AdminAreaRegistration.cs (since our area was named Admin).

    using System.Web.Mvc;
    
    namespace Mvc_Default.Areas.Admin
    {
        public class AdminAreaRegistration : AreaRegistration
        {
            public override string AreaName
            {
                get
                {
                    return "Admin";
                }
            }
    
            public override void RegisterArea(AreaRegistrationContext context)
            {
                context.MapRoute(
                    "Admin_default",
                    "Admin/{controller}/{action}/{id}",
                    new { action = "Index", id = UrlParameter.Optional }
                );
            }
        }
    }
    Code snippet 13-15.txt

You can use the AreaRegistration class for your area to register any area-specific routes.

PERFORMANCE: ASYNCHRONOUS CONTROLLER ACTIONS

The new AsyncController class provides support for asynchronous action methods. Asynchronous action methods allow you to make long-running calls from within your Controller actions without tying up a thread. A common use case for asynchronous action methods is to make calls to a long-running, external web service.

For more information on using the AsyncController class, see our discussion in Chapter 5.

MISCELLANEOUS

The following is a list of miscellaneous changes that are new in ASP.NET MVC 2:

  • Overriding the HTTP Method Verb: You can override the HTTP verb for form submissions by using the HttpMethodOverride Helper. This is covered in detail in Chapter 5 in the section "Simulating RESTful Verbs."

  • Optional and Default Parameter Values: ASP.NET MVC 2 allows you to specify optional and default parameter values. See the discussion in Chapter 5 for more information.

  • Empty Project Template: When you create a new ASP.NET MVC 2 application, you now have the option of creating an ASP.NET MVC 2 Empty Web Application. This project template creates a bare bones MVC 2 application, with just the folder structure, Global.asax, Scripts, and Site.css (see Figure 13-8). While the standard MVC 2 Application project template can help you get going on a new site, the Empty template is great when you know you're not going to be using features like the Account Controller, built in styles, etc.

  • New Visual Studio Code Snippets: ASP.NET MVC 2 includes 19 new code snippets, including the Code Snippets in Views we discussed earlier in this chapter. To view the list, in the Tools menu, select "Code Snippets Manager" (see Figure 13-9). For the language, select HTML, and for location, select ASP.NET MVC 2.

    FIGURE 13-8

    Figure 13.8. FIGURE 13-8

    FIGURE 13-9

    Figure 13.9. FIGURE 13-9

SUMMARY

ASP.NET MVC 2 adds to the Framework established in ASP.NET MVC 1.0, adding the kind of productivity features that make it...well, just plain fun to use. ASP.NET MVC 1.0 was exciting and enjoyable, but just a bit more work than it needed to be, and ASP.NET MVC 2 more than addresses that. We hope that you'll agree that ASP.NET 2 is so easy to work with that it just might be more fun. Don't worry — we won't tell your boss.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset