Simone Chiaretta
What’s New in ASP.NET MVC 2? Wiley Publishing, Inc. Updates, source code, and Wrox technical support at www.wrox.com
Contents
1 2
2 5 7 8 10 10
15 18 18 18 19 21
22 24 25 25 26 26 27 27 28
29 30 31 32
What’s New in ASP.NET MVC 2? Introducing ASP.NET MVC 2 ASP.NET MVC is the new Framework for building web applications released by Microsoft at the beginning of 2009. It adopts an approach that is completely different from the traditional Web Forms one that was introduced with the release of the .NET Framework in 2002: Instead of using an event-based programming style, ASP.NET MVC implements the Model-View-Controller pattern so that it’s easier to achieve a better separation of concerns and it’s easier to make the application extensible and testable. Version 1.0 has been downloaded and used by almost 1 million developers in the first year after its final release, becoming more and more popular every month. But some thought that it was still too unfinished for enterprise development, mainly because it was missing some important features like client-side validation. In March 2010, after one year, version 2 of the Framework has been released, whose main focus is to enhance productivity and make the Framework ready for enterprise development. ASP.NET MVC 2 is built on top of the first release and is completely compatible with it, which means that all of your knowledge, skills, and even the code you wrote continue to work when you move forward to the new version. ASP.NET MVC 2 adds the following new features, which will be covered in detail through the rest of this Wrox Blox: ❏
Templated Helpers — Make it easier and faster to create the HTML to display and edit data.
❏
Client-Side Validation — Brings the same powerful metadata-based validation to the client.
❏
Areas — Allow you to organize a big application into smaller logical sections.
What’s New in ASP.NET MVC 2? ❏
Asynchronous Controllers — Solve the same issue addressed by the asynchronous pages in traditional Web Forms by performing long-running tasks on a separate thread to free up resources on the server.
❏
Child Actions — Make easy encapsulation of logic and presentation possible.
❏
Strongly Typed UI Helpers — Add a new strongly typed flavor to the existing UI Helpers.
And in addition to these main features, other smaller features and enhancements to the existing ones have been introduced, together with a better Visual Studio (VS) integration, especially in VS2010. To make the migration to the new version easier, ASP.NET MVC 2 can be installed side-by-side with ASP.NET MVC 1.0, which means that on the same server you can have applications written in V1 and others written in V2, making it easier to adopt the new version for new applications while taking your time to upgrade the older ones. This Wrox Blox will not discuss what the ASP.NET MVC Framework is and how it works, but it is meant to guide a developer who is already familiar with the first version of the Framework through the new features that have been introduced into the second version. If you want to learn the basics of the Framework or if you find yourself struggling with some concepts while you are reading this Wrox Blox, I suggest that you read one of the books that treat the first version — such as Beginning ASP.NET MVC 1.0 by myself and Keyvan Nayyeri (Wrox, 2009; ISBN: 978-0-470-43399-7) or Professional ASP.NET MVC 1.0 by Rob Conery, Scott Hanselman, Phil Haack, and Scott Guthrie (Wrox, 2009; ISBN: 978-0-470-38461-9) — and then come back to read about the new features.
Runs on Visual Studio 2008 and Visual Studio 2010 ASP.NET MVC 2 can be used in Visual Studio 2008 (VS 2008), running on .NET 3.5 Service Pack 1, and in the newly released Visual Studio 2010 (VS 2010), running on .NET 4. If you want to use MVC 2 with Visual Studio 2010, there is nothing you have to do: The Framework comes in-the-box with the new Integrated Development Environment (IDE). But if you want to use MVC 2 with Visual Studio 2008, you have to download it from www.asp.net/mvc/ or via the Web Platform Installer. Other than this different way of getting the Framework, there are a few subtle differences between running on .NET 3.5 SP1 and on .NET4 because some features are available only with .NET 4. All the samples and code snippets will be developed with VS 2010 and .NET 4, but when the syntax is different or when a feature is available on .NET 4, it will be made very clear in the text.
Templated Helpers and Model Metadata With the goal to enhance productivity, Templated Helpers are probably the most important new feature of MVC 2. Templated Helpers automatically build the UI needed to represent or edit data based on the data model itself and on a set of metadata that, in the default implementation, is retrieved from the attributes applied to the Model.
What’s New in ASP.NET MVC 2? For example, a property of the data model can be marked with an attribute that specifies that the property represents HTML text. The Templated Helper can automatically render the text using a user interface (UI) specifically designed for HTML code instead of just treating it as a normal string, or it could present the user with a WYSIWYG textbox to edit the string instead of a normal textbox. All the magic is done via the following HTML Helpers: ❏
Html.EditorFor — Builds the UI to edit each property in the object specified as the parameter.
❏
Html.DisplayFor — Builds the UI to display the value of each property in the object specified
as the parameter. These two methods accept a lambda expression as the parameter, but if you prefer, there are also another two flavors: ❏
Html.Editor and Html.Display — Do the exact same thing as the EditorFor and DisplayFor, but these are used if you prefer to pass the name of the object as a string instead
of as an expression. ❏
Html.EditorForModel and Html.DisplayForModel — Works directly on the full Model
of the View. Listing 1 shows how to write the form to edit the Model of a View.
Listing 1: The Edit View using the Templated Helpers Fields
Listing 2 contains the definition of the View Model, with the attributes applied to its properties.
Listing 2: The Post View Model using System.Web.Mvc; using System.ComponentModel.DataAnnotations; using System.ComponentModel; public class Post
continued
What’s New in ASP.NET MVC 2? Listing 2: The Post View Model (Continued) { public int Id { get; set; } [DataType(DataType.MultilineText)] public string Content { get; set; } public string Title { get; set; } [DisplayName(“Post Category”)] public int CategoryId { get; set; } [ScaffoldColumn(false)] public string EditedBy{get; set;} }
Figure 1 shows the editing form that is rendered when this View Model is supplied to the View shown in Listing 1.
Figure 1: Editing form rendered using the Templated Helpers.
The Post object shown in Listing 2 shows another important aspect of the UI templates: Their behavior can be modified using attributes applied to the properties of the class. The listing contains some of the attributes available in the DataAnnotation namespace. The following list explains what they do:
What’s New in ASP.NET MVC 2? ❏
DataType — Allows you to specify the contents of the field in a way that is more specific than the type of the property. The type name is selected from the DataType enumerator (which, among the various possibilities, contains EmailAddress, Html, MultilineText, Url, etc.).
❏
DisplayName — Sets the name with which the property has to be identified in the UI.
❏
ScaffoldColumn — Specifies whether a property will be rendered in the UI.
This Wrox Blox shows how to control the rendering of the data model via DataAnnotations attributes applied to properties of the class. The way this metadata is retrieved is totally extensible, and in the future there might be other ways to control the rendering, for example, via Fluent API or via XML.
HiddenInputAttribute One attribute that deserves a special note is the HiddenInputAttribute. This attribute indicates that a Hidden field has to be rendered when displaying the Edit form for a Model. Optionally, you can also specify whether to display the value of the field with the DisplayValue property. This attribute also controls how the field must be rendered in a display form. The following list explains which is the result of applying the attribute with the DisplayValue property set to true (which is the default value) and then to false in the two scenarios (Display and Edit form): Display form scenario ❏
DisplayValue=true — The value of the property and its name are rendered, like any
other property. ❏
DisplayValue=false — Nothing is rendered, as if the property were not in the Model.
Edit form scenario ❏
DisplayValue=true — The hidden field is rendered in the page, and the value of the property
is shown as if it were a display template (value as normal text and label with the name of the property). ❏
DisplayValue=false — Only the hidden field is rendered, with no visible hint of the field
being displayed. This all might sound a bit complicated. The next listings will show an example of how things work. Listing 3 contains the View Model that will be used to show the effect of the various combinations of DisplayValue and templates.
Listing 3: The sample View Model public class ItemViewModel { [HiddenInput] //same as [HiddenInput(DisplayValue=true)] public int Id {get; set;} public string Name {get; set;} [HiddenInput(DisplayValue=false)] public string EditedBy {get; set;} }
What’s New in ASP.NET MVC 2? When you display an edit template for the View Model shown in Listing 3 using Html.EditorForModel, the HTML rendered is the same as what is shown in Listing 4.
Listing 4: HTML rendered in an edit template Name Id 1
Notice that the Id field is rendered using both the label and the hidden field in the edit template and only with the label (like any other normal field) in the display template. The EditedBy property, instead, is rendered only as a hidden field in the edit template and not rendered at all in the display one. This attribute is useful in the following scenarios: ❏
You want to display the value of a property of your View Model and you want the value to be available when the form is sent back to the server, but you don’t want the user to be able to modify it (e.g., the ID of a product).
❏
You need the value of a property to be sent back to the server, but you don’t want the user to see its value (like a timestamp for handling concurrency).
What’s New in ASP.NET MVC 2?
How to Write Custom Templates Although the default templates are useful on their own, being able to write your own templates adds the ability to customize the exact rendering of all your data types. A template is nothing more than a Partial View that is stored in a specific location: ❏
/Views/ControllerName/DisplayTemplates — Contains the Controller-specific templates used by the display helpers.
❏
/Views/ControllerName/EditorTemplates — Contains the Controller-specific templates used by the editor helpers.
❏
/Views/Shared/DisplayTemplates — Contains all the shared (i.e. that can be used by all controllers) templates used by the display helpers.
❏
/Views/Shared/EditorTemplates — Contains the shared templates used by the editor helpers.
For the Templated Helpers to work, the filename of the Partial View can either be the name of the type for which the custom template is being designed, or, if the custom template is only for some instances of a type, an attribute can be applied to the View Model to instruct the helper to use a specific template instead of the default one. This attribute is UIHint. Building on Listing 2, you can try to make the editing of a Blog Post easier than entering the ID of the category. Implement a template that renders a dropdown list to choose the Post type. First, the UIHint attribute must be applied to the CategoryId property of the Post model: [UIHint(“Category”), DisplayName(“Post Category”)] public int CategoryId { get; set; }
Then the custom template must be written and saved as Category.ascx in the EditorTemplate folder. Listing 6 shows the code for it.
Listing 6: The Category.ascx custom template
This is quite similar to any other dropdown list, but there are a few things to highlight. First, this is a strongly typed Partial View of type int, which is the type of the property that will be rendered with this template.
What’s New in ASP.NET MVC 2? Then notice that there is no field name specified in the dropdown list: This is because the template could be reused, and each time the name of the field to edit will be different. Figure 2 shows the same editing form of Figure 1, but with a dropdown list used to select the type of the post (instead of just the numeric ID).
Custom Edit Template
Figure : Edit form with custom template.
In the example in this section, a custom display template doesn’t make a lot of sense because it would normally render just the category’s ID anyway, but the same concept can be applied to it as well. The template shown in the next code snippet renders the string “Category with id” and then the ID of the category instead of just the ID itself. Category with id =
The template needs to be called Category.ascx as with the edit template, but this time it must be saved into the DisplayTemplates folder.
Validation Still using the same approach used to render the UI, MVC 2 allows you to specify validation rules via attributes, using the DataAnnotation namespace (and, as with the UI rendering, this is totally extensible and replaceable if you prefer to use other ways of specifying your validation rules).
What’s New in ASP.NET MVC 2? Listing 7 adds to the Post object of Listing 2 the attributes for the validation.
Listing 7: The Post model with both UI and validation attributes applied public class Post { [Range(0, 100),Required] public int Id { get; set; } [DataType(DataType.MultilineText)] public string Content { get; set; } [StringLength(15, ErrorMessage = “The {0} field is too long”)] [Required] public string Title { get; set; } [UIHint(“Category”), DisplayName(“Post Category”), Required] public int CategoryId { get; set; } [ScaffoldColumn(false)] public string EditedBy{get; set;} }
Listing 7 shows three validation attributes: ❏
Required — The Required attribute, as the name implies, makes the property to which it is
applied, required. Another way to specify that property is required is to use a non-nullable type. In that case, since the property is an int (and it’s not nullable), the Required attribute could have been avoided, but I added it for the sake of clarity. ❏
Range — The Range attribute allows you to specify a range inside which the property
is valid. ❏
StringLength — The StringLength attribute lets you specify the maximum length
of a string. Each attribute, in addition to the properties needed to configure the validation, has the three following properties: ❏
ErrorMessage — Specifies a fixed error message.
❏
ErrorMessageResourceName and ErrorMessageResourceType — Let you specify a
localizable error message. Once the model is configured, you can perform the validation as usual, because all the rules will be taken into account by the Framework: public ActionResult Edit(Post post) { if (ModelState.IsValid) { // The input is valid
What’s New in ASP.NET MVC 2? _repository.UpdatePost(post); return RedirectToAction(“Edit”, new { id = post.Id }); } // Re-display the view so that the user can correct errors return View(post); }
Client-Side Validation What you just read was only about server-side validation, but in MVC 2 the same rules can be easily transferred to the browser to perform client-side validation. All you have to do is include the two needed JavaScript libraries and enable client-side validation by calling the Html.EnableClientValidation helper, as shown in Listing 8.
Listing 8: Edit View with client-side validation enabled <script src=”/Scripts/MicrosoftAjax.js” type=”text/javascript”> <script src=”/Scripts/MicrosoftMvcValidation.js” type=”text/javascript”> Edit Fields
The two JavaScript files that need to be referenced are MicrosoftAjax.js, which contains the core JavaScript libraries needed by all the other JavaScript files used in ASP.NET MVC, and MicrosoftMvcValidation.js, which is the one responsible for performing the validation.
Custom Validation Rules The last piece of the puzzle is building custom validation rules. A custom validation rule is made of three parts — one needed for the attribute and server-side validation; one needed for the client-side validation; and the last one, called an adapter, used to send the validation rule to the client.
10
What’s New in ASP.NET MVC 2? For the sake of this example, I’m going to write a custom validator that checks whether the text of the field contains the word Wrox. The first piece to implement is the attribute. The custom validator has to inherit from ValidationAttribute and implement the IsValid method. Listing 9 shows the code for the WroxAttribute.
Listing 9: Custom validation attribute WroxAttribute public class WroxAttribute : ValidationAttribute { public override bool IsValid(object value) { if (value == null) return true; var text = value as String; if (text == null) return false; return (text.ToLower().Contains(“wrox”)); } }
The validation method, IsValid, returns false if the text doesn’t contain the word Wrox or if the attribute was mistakenly applied to a property that was not a string. It returns true if the text contains the word Wrox, but also if the value is null: This means that the property didn’t have a value, which is a perfectly valid situation for our custom validator because it’s the Required attribute that has to check whether the field has a value or not. The JavaScript function performs the same validation on the client, shown in Listing 10.
Listing 10: The JavaScript file with the custom validation function Sys.Mvc.ValidatorRegistry.validators[“wrox”] = function (rule) { return function (value, context) { if (value.toLowerCase().indexOf(“wrox”) > -1) return true; else return rule.ErrorMessage; }; };
Listing 10 not only contains the validation function, but also registers it in the list of validators for the page using a name to identify the validation type, in this sample, wrox. A good practice is to put all your custom validation types in an external script file and include it together with all the other JavaScript files needed for client-side validation.
11
What’s New in ASP.NET MVC 2? The last piece of the custom validator is the adapter. Its responsibility is to send the validation rule parameters to the client: It must inherit from DataAnnotationsModelValidator and must override its GetClientValidationRules method. See Listing 11.
Listing 11: The adapter class public class WroxValidator : DataAnnotationsModelValidator<WroxAttribute> { string _message; public WroxValidator(ModelMetadata metadata, ControllerContext context, WroxAttribute attribute) : base(metadata, context, attribute) { _message = attribute.FormatErrorMessage(metadata.PropertyName); } public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { var errorRule = new ModelClientValidationRule() { ErrorMessage = _message, ValidationType = “wrox” }; return new[] { errorRule }; } }
The ValidationType property of the validation rule must contain exactly the same key with which the client-side validation function has been registered through JavaScript in Listing 10, as this is how the link between the server-side validation and the client-side validation happens. Last but not least, the validation attribute and the validation adapter must be registered inside the validation provider. This must be done in the Application_OnStart event handler in the Global.asax.cs file: DataAnnotationsModelValidatorProvider.RegisterAdapter( typeof(WroxAttribute), typeof(WroxValidator));
Now that the application has been set up with all the needed pieces, the last step is to apply the new attribute to the Post model and display the editing form, which is shown in Figure 3: [Wrox(ErrorMessage=”The {0} field must contain the word Wrox”)] [DataType(DataType.MultilineText)] public string Content { get; set; }
2
What’s New in ASP.NET MVC 2?
Figure : The edit template showing the validation errors.
A validation rule that cannot be configured can be good in some situations, but most of the time you might want to able to supply some configuration parameters. Building upon Listing 9, Listing 10 and Listing 11, Listing 12 will show how to write a configurable validation rule called ContentAttribute. Listing 12 contains the code for the validation attribute and the server-side validation rule.
Listing 12: Configurable Validation Attribute ContentAttribute public class ContentAttribute : ValidationAttribute { public string Text { get; set; } public override bool IsValid(object value) { if (value == null) return true; var text = value as String; if (text == null) return false; return (text.ToLower().Contains(Text)); } public override string FormatErrorMessage(string name) { return String.Format(“The {0} field must contain the word ‘{1}’”, name, Text); } }
If you compare it with Listing 9, you will notice two differences (the lines highlighted): The first is the property Text which contains the string required. The second is the FormatErrorMessage method
3
What’s New in ASP.NET MVC 2? which returns the default error message to be displayed when the validation fails. It receives the name of the field that is being validated as the input parameter. Then the client-side JavaScript validation function has to be modified as well. Listing 13 shows how.
Listing 13: Client-side validation function Sys.Mvc.ValidatorRegistry.validators[“content”] = function (rule) { var text = rule.ValidationParameters[“text”]; return function (value, context) { if (value.toLowerCase().indexOf(text) > -1) return true; else return rule.ErrorMessage; }; };
As in Listing 10, the validation function is registered in the list of validators using a unique key, content in this sample. But there is also another difference: The string that the validation function must find in the field that it is validating is retrieved from the ValidationParameters hashtable, using the key text. Finally, as with the WroxAttribute, the adapter to send the validation rule parameters to the client must be written. Listing 14 shows the ContentValidator class.
Listing 14: The adapter class for the ContentAttribute public class ContentValidator : DataAnnotationsModelValidator { string _message; string _text; public ContentValidator(ModelMetadata metadata, ControllerContext context, ContentAttribute attribute) : base(metadata, context, attribute) { _text = attribute.Text; _message = attribute.FormatErrorMessage(metadata.PropertyName); } public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { var errorRule = new ModelClientValidationRule() { ErrorMessage = _message, ValidationType = “content” }; errorRule.ValidationParameters.Add(“text”, _text.ToLower()); return new[] { errorRule }; } }
4
What’s New in ASP.NET MVC 2? There are two differences compared to the other adapter class of Listing 11: In the constructor the text to be verified is retrieved from the ContentAttribute, and then in the GetClientValidationRules method, it is added to the ValidationParameters hashtable of the validation rule (with the key text). Finally, after you register the new validation adapter, you can apply the attribute to the View Model. [Content(Text=”simone”)] [DataType(DataType.MultilineText)] public string Content { get; set; }
Areas One of the complaints about MVC 1 was that once the application grows in size (with many controllers, views, and view models), you end up with dozens of files in the Controllers folder and even more in the Models folder, making the maintenance of the web project very complicated. To solve this problem, MVC 2 adds the concept of areas. An area is a functional unit that groups together the different parts of an MVC application that pertain to the same logical group. For example, an ecommerce web application can have an area for the user management, one for the product listing, one for the purchase process, one for the backend, and so on. Adding an area to your ASP.NET MVC 2 application is pretty easy: All you have to do is click on the Add Area item in the contextual menu, as shown in Figure 4. Next, type in the name of the area in the dialog that appears, as shown in Figure 5, and Visual Studio (both 2008 and 2010) will then create all the plumbing for you.
Figure : Contextual menu with “Add Area.”
5
What’s New in ASP.NET MVC 2?
Figure : “Add Area” dialog.
You will end up with the project structure depicted in Figure 6: a new folder named Areas under which you will find all the areas you created, each containing the usual Controllers, Models, and Views folders.
Figure : Project structure.
Together with the folders, each area contains a file named AreaNameAreaRegistration.cs, which is where the routing rules specific for the area are defined. Listing 15 shows its contents.
6
What’s New in ASP.NET MVC 2? Listing 15: Routing definition file for the area, BlogAreaRegistrations.cs using System.Web.Mvc; namespace Area_Sample.Areas.Blog { public class BlogAreaRegistration : AreaRegistration { public override string AreaName { get { return “Blog”; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( “Blog_default”, “Blog/{controller}/{action}/{id}”, new { action = “Index”, id = UrlParameter.Optional } ); } } }
Notice that the AreaRegistration class also contains a property named AreaName. By convention, it must return the name of the area and, to keep everything more maintainable, it’s recommended that it is also the name of the folder. But you don’t need to worry about setting it right, as the IDE will write all that plumbing code for you. The last step needed in order for this new feature to work is adding to the Application Startup event (in the Global.asax.cs file in the root folder of the application) the line of code that registers all the areas: AreaRegistration.RegisterAllAreas();
Once an area is configured, all the rest is taken care of by the ASP.NET MVC run time, and you can write your application as you always did before. The only time you have to remember about areas is when you link between areas or from the root of the application to an area or if you want to retrieve the URL for an action that has been defined outside the current area. In order to link to a different area, you have to specify in which area you want the link to point. This is done by passing to the ActionLink HTML Helper method an anonymous object as value for the routeValue parameter:
7
What’s New in ASP.NET MVC 2? Note that the last parameter, null, is needed only because the ActionLink method overload that has the routeValue parameter also has the htmlAttribute parameter and, in case it’s not needed, can be left as null.
Asynchronous Controllers Asynchronous Pages is a feature that already existed in Web Forms, introduced to make it possible to process multiple concurrent long-running requests without blocking the web server (this situation is called thread starvation). The same feature has been introduced in the MVC Framework in version 2 with a slightly different name: Asynchronous Controllers. Before explaining how to implement an async controller, let’s briefly examine why they are needed.
Why Asynchronous Operations Matter When a request arrives, the web server (IIS) passes it to the ASP.NET worker process, which picks a thread from the ThreadPool to execute it. Until the request is being processed, the thread is blocked and cannot execute other requests. Usually this is not a problem as the thread pool can be made large enough to accommodate most scenarios, but the number of threads is in any case limited, and in scenarios in which there are many concurrent long-running requests, the ThreadPool can run out of threads. When this happens the web server itself starts queuing up the requests, but, again, at a certain point this queue also becomes full, and the users will start to receive the “Server too busy” error. For a better understanding of how this process works in detail, I recommend reading the blog post: “ASP.NET Thread Usage on IIS 7.0 and 6.0” (http://blogs.msdn.com/tmarq/archive/2007/07/21/ asp-net-thread-usage-on-iis-7-0-and-6-0.aspx). How can asynchronous requests help in these scenarios? Usually long-running requests take long to execute because they are waiting for information from other systems, typically because they are calling external web services or doing other kind of network access: While they are waiting for the I/O operation to complete, they are just blocking the thread, preventing other requests from being executed, but without doing anything.
The Anatomy of an Asynchronous Controller To solve the long-running request scenario, an action living in an Async Controller is made by two separate methods:
1. .
The first method receives the input and then starts the asynchronous operation (on a separate thread) for the long-running I/O bound operation. The second method is invoked automatically by the Framework when the async operation completes and is the one responsible for sending the response back to the browser.
When the first method ends, the thread is returned to the thread pool so that it can be available for processing other requests.
8
What’s New in ASP.NET MVC 2? It’s important to point out that the end user always perceives the request as synchronous: It’s asynchronous only on the server and doesn’t have anything to do with AJAX.
How to Implement an Async Controller Enough theoretical talk! Now you will write an async action. The first difference with a normal action is that the Controller, instead of inheriting from Controller, has to inherit from AsyncController. Then the action, as said in the previous section, is made by two methods: The first, the one that handles the request from the browser, is called ActionNameAsync; whereas the second, the one that sends the response back to the client, is called ActionNameCompleted. Listing 16 shows the PurchaseController that contains the action Buy that calls an external service to check whether a product is available for purchase.
Listing 16: PurchaseController with the Buy action public class PurchaseController : AsyncController { private readonly ServiceClient _client = new ServiceClient(); public void BuyAsync(int productId) { AsyncManager.OutstandingOperations.Increment(); _client.CheckAvailabilityCompleted += (sender, e) => { AsyncManager.Parameters[“isAvailable”] = e.Result; AsyncManager.Parameters[“oldThreadID”] = Thread.CurrentThread.ManagedThreadId; AsyncManager.OutstandingOperations.Decrement(); }; _client.CheckAvailabilityAsync(productId); } public ActionResult BuyCompleted(bool isAvailable, int oldThreadID) { string message = “The product is NOT available”; if (isAvailable) message = “The product is available”; message += “
Initial thread: “ + oldThreadID; message += “
Final thread: “ + Thread.CurrentThread.ManagedThreadId; return View(model: message); } }
9
What’s New in ASP.NET MVC 2? The first method is the entry point of the action, and it calls a Windows Communications Foundation (WCF) web service asynchronously. But there are a few things specific to how async actions work: ❏
Before calling the service, the number of outstanding operations is increased (I’ll explain this later).
❏
In the handler for the Completed event, the result of the call to the external service is stored into the Parameters hash table, and finally the number of outstanding operations is decremented.
❏
Also notice that it returns void, instead of ActionResult as normal actions.
The second method is automatically invoked by the run time, and its parameters are the ones that were stored in the Parameters hash table by the Completed event handler. Apart from that, it’s similar to any other action: It receives the result from the external service, formats it into the View Model, and finally returns the action result. One part of the whole process that feels a little strange and can be avoided is the manual incrementing and decrementing of the outstanding operations count. The reason is that the Completed part of the action is invoked by the run time when the OutstandingOperations count reaches zero, and since it is possible to call more than one external service asynchronously, there would be no way for the ASP.NET run time to understand how many operations were initiated and when those are complete. Listing 13 also contains some lines of tracing code to show that the two parts of the async action are executed in two different threads. Figure 7 shows the results of the execution of the action.
Figure : Result of the Async Action.
0
What’s New in ASP.NET MVC 2? Adding a Reference to a WCF Service with Async Operations If the WCF service doesn’t expose the async operations, don’t panic. When you add a reference to an external WCF service, you don’t get the async operations by default, but you have to configure the service reference. To do so, right-click on the name of the WCF service you are referencing, select “Configure Service Reference” in the contextual menu, and flag the checkbox “Generate asynchronous operations” in the dialog that appears (Figure 8).
Figure : The “Configure Service Reference” dialog.
When to Use an Async Controller Now that you know how to implement an async controller, I’ll explain why you might want to do it. As a rule of thumb, write an async action when you have long-running operations that take long to complete because of I/O wait time rather than CPU computation. Another possible scenario for using async actions is when you have to call various external services in parallel. If you had to call three different services, with a synchronous action they would have been called one after the other, while in an async action they can be called in parallel, taking one-third of the time.
1
What’s New in ASP.NET MVC 2?
RenderAction and Child Actions Probably the biggest complaint brought to ASP.NET MVC 1.0 was the lack of an easy way to have some kind of reusable components: With the RenderPartial helper, you can encapsulate portions of a View, but there was no easy way to encapsulate the logic also. The new version of the Framework introduces a concept that tries to solve this issue: child actions. A child action is an action that is invoked from inside a View, using the Html.RenderAction helper method, and whose result is rendered inside the View that invoked it. The following sample shows you how to write a component that contains both logic and presentation using the RenderAction method. Listing 17 shows the Controller with both the main and the child action.
Listing 17: BlogController with the two actions needed public class BlogController : Controller { InMemoryPostModel _repository = new InMemoryPostModel(); public ActionResult Show(int id) { var model = _repository.GetPost(id); return View(model); } [ChildActionOnly] public ActionResult ListByCategory(int categoryId) { var model = _repository.GetPostByCategory(categoryId); return PartialView(model); } }
As you can see, the action Show, the one that displays a single post, doesn’t gather any data to display the list of posts for the category of the current post, but all the logic to retrieve the posts for a given category is inside the child action named ListByCategory. Since ListByCategory is a normal action, someone could type its URL directly into the web browser’s address bar and retrieve the list of posts. This might be something that you want to avoid. You can decorate the action method with the attribute [ChildActionOnly] to allow the action to be executed only as a child action and not as a normal action invoked by a URL. Listing 18 shows how the child action ListByCategory is called in the View.
22
What’s New in ASP.NET MVC 2? Listing 18: The Show View Show
This article was posted in the category with id
Similarly to what the Show action does, the main view Show.aspx doesn’t know how to display the list of posts. This task is delegated to the action ListByCategory, which is called using the RenderAction method, and the ID of the category is passed as an anonymous object. Notice the similarity of this method to the methods that are used to link to other actions: You specify the action name and the Controller name, and then you can specify the usual routeValue parameter as an anonymous type. Finally, Listing 19 shows the code for the ListByCategory.ascx View.
Listing 19: The Partial View ListByCategory.ascx ” %>
This is just a standard Partial View, which is completely unaware of what sits behind. Figure 9 shows the rendered page and highlights which areas of the page have been rendered by which action.
23
What’s New in ASP.NET MVC 2?
Rendered by the child action
Rendered by the main action
Figure : Show View with Partial View rendered by a child action.
Strongly Typed UI Helpers If you don’t need the power of the editor and display templates (or you want to write your own templates) or you want to manually control how a form is rendered, ASP.NET MVC 2 introduces a new strongly typed version of the HTML UI helpers. When inside a strongly typed View, if you want to render a textbox to edit the value of the property Name, you can write: Html.TextBoxFor(m => m.Name)
instead of Html.TextBox(“Name”)
This new option has quite a few benefits over the string-based version: ❏
Writing code is less error-prone as you have the help of the IntelliSense.
❏
Maintenance cost is reduced because if you change the name of the property from Name to FirstName, the refactoring tool can also rename this instance of the variable, something that is not possible with the other option because it relies on a normal string.
❏
Debugging is also easier because a mistype in the name of the property can be easily discovered owing to a compilation error (at run time or even at compile time if precompilation of View is enabled) rather than being discovered only when someone notices that some text is missing.
Besides the TextBox helper, all the other UI helpers also have their strongly typed counterparts:
24
What’s New in ASP.NET MVC 2? ❏
TextBox, TextBoxFor<M>
❏
TextArea, TextAreaFor<M>
❏
Password, PasswordFor<M>
❏
Hidden, HiddenFor<M>
❏
CheckBox, CheckBoxFor<M>
❏
DropDownList, DropDownListFor<M>
❏
ListBox, ListBoxFor<M>
❏
RadioButton, RadioButtonFor<M>
❏
Label, LabelFor<M>
All these UI helpers work exactly the same as the string-based counterpart available in ASP.NET MVC 1.0.
Enhancements to ModelBinders The last component to which ASP.NET MVC 2 brought some new features is model binding. Now there is the possibility to specify an optional parameter with a default value, to enforce that an action can be called only through a secure connection and to bind binary data as an action parameter. Start by looking at the new Default Value feature.
Default Value Imagine this pretty common scenario: You have an action that retrieves a paged list of items, and you want the URL for the first page to be /Blog/List, but you also want to be able to specify a following page with the URL /Blog/List?page=2. With ASP.NET MVC 1.0, you would have had to handle the first page case yourself. Now with MVC 2, there is a new attribute you can use to specify which value to assign to an action parameter in case it was missing — the DefaultValueAttribute. Just apply the attribute to the parameter you want to have the default value for, and specify the default value as the parameter of the attribute, as in the following code: public class BlogController: Controller { public ActionResult List([DefaultValue(1)] int page) { var list = GetPostsByPage(page); return View(list); } }
25
What’s New in ASP.NET MVC 2? The page variable will have 1 as the value if no parameter can be retrieved from the HTTP request; otherwise, the value supplied will be used. If you are using .NET 4, you can also use the new syntax of C# 4 for optional parameters. The same code will be written as follows: public class BlogController: Controller { public ActionResult List(int page = 1) { var list = GetPostsByPage(page); return View(list); } }
Increasing Security with HTTPS A new attribute has been introduced to help make your application more secure — RequireHttpsAttribute. When applied to an action or to a Controller, it re-directs a non-secure request (non-SSL) to the secure equivalent on SSL (HTTPS): public class HomeController : Controller { [RequireHttps] public ActionResult Edit() { return View(); } }
Avoiding the JSON Array Attack JavaScript Object Notation (JSON) has a subtle vulnerability. Whereas a normal JSON object cannot be executed as normal JavaScript code, a JSON array can. This behavior, combined with the Cross-Site Request Forgery (abbreviated as CSRF or XSRF), can allow an attacker to gain access to private information. This was a very quick explanation of the exploit, but if you want more detail on the topic, I recommend you read this nice blog post by Phil Haack, which explains the vulnerability and how it can be exploited: http://haacked.com/archive/2009/06/25/json-hijacking.aspx. Unlike many other attacks, this one is pretty easy to prevent. All you have to do is avoid requesting JSON responses via GET, but make them only via POST. To make sure you do it right, in MVC 2 a JsonResult by default cannot be returned if the action was invoked via a GET request. But in case you really need to return a JSON object with GET, you can state your intention using an overload of the Json method: return Json(model, JsonRequestBehavior.AllowGet);
26
What’s New in ASP.NET MVC 2?
Short Notation for AcceptVerbs Attribute ASP.NET MVC 2 introduces a short-hand notation to specify the HTTP verbs to which an action must respond. In the previous version of ASP.NET MVC there was the AcceptVerbs attribute. To specify that an action had to be invoked only in a POST request, you had to write: [AcceptVerbs(HttpVerbs.Post)] public ActionResult Update(Item item)
Now you can achieve the same result with less typing: [HttpPost] Public ActionResult Update(Item item)
There are the short-hand notations also for all the other HTTP verbs: ❏
[HttpGet]
❏
[HttpPost]
❏
[HttpPut]
❏
[HttpDelete]
Overriding the HTTP Verb If you are building a Representational State Transfer (REST)–style application, you rely on the HTTP verbs in order to understand which operation to perform on a given resource. As just explained, you can use some attributes to select the correct action to invoke based on the verb. This works great if you are building a Web API, but if you want to adopt the same architectural style also on a web application, you face a problem: Regular browser requests only support GET and POST, but don’t support PUT and DELETE. However, to enable RESTful requests in normal POST operations, ASP.NET MVC 2 introduces a HTML helper to override the POST verb and emulate any other verb — Html.HttpMethodOverrideverb). It injects a hidden field named X-HTTP-Method-Override that is taken into account by the AcceptVerbs attribute (and the other HTTP verb attributes) to identify the action to invoke. For example, if you want to emulate a DELETE request, you have to write the following code in your View:
27
What’s New in ASP.NET MVC 2? This code will render the following HTML:
Finally, the AcceptVerbs attribute (and the other HTTP verb attributes) will behave as if it were an actual DELETE request: [HttpDelete] public ActionResult Delete(int id){...}
Binary Binding The last binding-related enhancement is about binary data. With the previous release of the Framework you have to handle the serialization of binary data or byte arrays yourself in a form. Now MVC 2 handles the serialization to base64 automatically. The encode side is handled by two new overloads of the Html.Hidden Helper method (one accepting a binary and the other accepting a byte array), whereas the decoding phase is handled by a new ModelBinder for binary and byte arrays. For example, you have a View Model with a TimeStamp property to avoid concurrency problems when editing the item, and it is defined as a byte array: public class BlogPost { //Other properties public byte[] TimeStamp {get; set;} }
When you write the editing form, you can write:
and it will be rendered to HTML something like this:
When the form is sent back to the server, the binder will do the magic and convert the base64-encoded string to its original value as a byte array. All this happens without any action needed by the developer as all the conversion is taken care of by the Framework: public ActionResult Edit(BlogPost post) { //Here post.TimeStamp will have its original value }
28
What’s New in ASP.NET MVC 2?
Automatic HTML-Encoding The last new feature of ASP.NET MVC 2 is something that works only with .NET 4: automatic HTMLencoding. This new ASP.NET 4 feature allows you to specify with very clean syntax that you want to HTML-encode a given string, thus greatly mitigating the chances of Cross Site Scripting (XSS) attacks. This is achieved through the new code block syntax alert(‘Injected’), the user would get the a JavaScript alert instead of just reading his name (Figure 10). This sample is not a big deal, but the code injected could have also been something that grabbed the user session cookie and e-mailed it to the attacker, who could have used it to gain access to some sensitive information.
Figure 10: Result of the XSS attack.
Fortunately, fixing this problem is pretty easy: All you have to do is sanitize (i.e., make all the html tags visible as plaintext) the string before rendering it. With the previous version of MVC, you had to write .
29
What’s New in ASP.NET MVC 2? But writing the Encode method every time some text is rendered in the page is boring and much too repetitive, and some people forget to write it. Now, with the new syntax introduced in ASP.NET 4.0, HTML-encoding a string is as easy as this: . But what happens if the auto-encode syntax is used with something that must return Html tags, for example, with ? To address this issue in .NET 4.0, a new interface has been introduced — IHtmlString. If a method returns a string, but it doesn’t want it to be HTML-encoded (which means that it already encoded it internally), then it has to return an IHtmlString instead of just String. This is the reason why all the HTML helpers in MVC 2 return MvcHtmlString (which implements the IHtmlString interface): They emit text that has already been sanitized and that doesn’t have to be encoded again.
Enhanced Visual Studio Tooling In addition to all the features brought to the library, MVC 2 also brings some enhancement to the Integrated Development Environment (IDE) side. Apart from the new dialogs added to support the new features (like Areas) and the updated T4 templates used to generate the code of views using the new strongly typed UI helper methods, with ASP.NET MVC 2 you also get an empty project template that you can use if you want to start your web application from the bare minimum with an empty project template, shown in Figure 11.
Figure 11: Empty project template.
0
What’s New in ASP.NET MVC 2? The project template only adds the references to the correct assemblies, the minimum entries in the web.config file, the JavaScript libraries, and the minimal view and CSS code; the rest is up to you. I recommend this approach only to experienced developers. Another interesting enhancement, but only available from inside Visual Studio 2010, is the new set of code snippets specifically made for MVC: Just type actionlink and press the Tab key, and the IDE will expand it as shown in Figure 12.
Figure 1: Expanded ActionLink code snippet.
Then you will be able to press the Tab key again to get to the various placeholders to enter the values for the parameters of the method. To see the complete list of MVC-specific snippets, open the Code Snippets Manager from inside the Tools menu, and select HTML as the language and ASP.NET MVC 2 as the location, as shown in Figure 13.
Figure 1: List of MVC snippets.
Summar y In this Wrox Blox you learned about all the new features that have been introduced into ASP.NET MVC 2 that were designed to make development more productive: ❏
Areas
❏
Templated Helpers and Model Metadata
1
What’s New in ASP.NET MVC 2? ❏
Client-Side Validation
❏
Asynchronous Controllers
❏
RenderAction and Child Actions
❏
Strongly-Typed UI Helpers
❏
Enhanced ModelBinders
❏
Automatic HTML Encoding
❏
Enhanced Visual Studio Tooling
All the samples from which the code listings are available for download on www.Wrox.com.
About Simone Chiaretta Simone Chiaretta is an Italian software architect and developer who enjoys sharing his development experience and more than 10 years’ worth of knowledge of Web development with ASP.NET and other Web technologies. He currently lives in Brussels, working as web architect and team lead for the Council of the European Union. Previously he worked for eight years as a Web developer for Esperia, a Web agency based in Milan, where he developed a CMS that still powers the AC Milan website. He’s also worked in New Zealand as chief software architect for Calcium Software Ltd. and later as Senior Solution Developer for Avanade Italy, an international consulting company jointly owned by Accenture and Microsoft. Simone is a Microsoft MVP in ASP.NET, an ASPInsider, and he has been involved in many open source projects, but now he focuses mainly on SubText to try to take it to the next level. Simone loves sharing his knowledge on his blog at http://codeclimber.net.nz and, if you can read Italian, also at http://blogs.ugidotnet.org/piyo. When not working or playing with technology, Simone spends time with his wife, Daniela, ice-climbing, free-climbing, and alpine and mountain climbing. The views expressed in this Wrox Blox are purely those of the writer and may not in any circumstances be regarded as stating an official position of the Council of European Union.
32
What’s New in ASP.NET MVC 2? Published by Wiley Publishing, Inc. 10475 Crosspoint Boulevard Indianapolis, IN 46256
www.wiley.com Copyright © 2010 by Wiley Publishing, Inc., Indianapolis, Indiana ISBN: 978-0-470-76748-1 No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance Center, 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to the Publisher for permission should be addressed to the Permissions Department, John Wiley & Sons, Inc., 111 River Street, Hoboken, NJ 07030, (201) 748-6011, fax (201) 748-6008, or online at http://www.wiley.com/go/permissions. Limit of Liability/Disclaimer of Warranty: The publisher and the author make no representations or warranties with respect to the accuracy or completeness of the contents of this work and specifically disclaim all warranties, including without limitation warranties of fitness for a particular purpose. No warranty may be created or extended by sales or promotional materials. The advice and strategies contained herein may not be suitable for every situation. This work is sold with the understanding that the publisher is not engaged in rendering legal, accounting, or other professional services. If professional assistance is required, the services of a competent professional person should be sought. Neither the publisher nor the author shall be liable for damages arising herefrom. The fact that an organization or Website is referred to in this work as a citation and/or a potential source of further information does not mean that the author or the publisher endorses the information the organization or Website may provide or recommendations it may make. Further, readers should be aware that Internet Websites listed in this work may have changed or disappeared between when this work was written and when it is read. For general information on our other products and services please contact our Customer Care Department within the United States at (877) 762-2974, outside the United States at (317) 572-3993 or fax (317) 572-4002. Trademarks: Wiley, the Wiley logo, Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates, in the United States and other countries, and may not be used without written permission. All other trademarks are the property of their respective owners. Wiley Publishing, Inc., is not associated with any product or vendor mentioned in this Wrox Blox. This PDF should be viewed with Acrobat Reader 6.0 and later, Acrobat Professional 6.0 and later, or Adobe Digital Editions. Usage Rights for Wiley Wrox Blox. Any Wiley Wrox Blox you purchase from this site will come with certain restrictions that allow Wiley to protect the copyrights of its products. After you purchase and download this title, you: • • • • • •
Are entitled to three downloads Are entitled to make a backup copy of the file for your own use Are entitled to print the Wrox Blox for your own use Are entitled to make annotations and comments in the Wrox Blox file for your own use May not lend, sell or give the Wrox Blox to another user May not place the Wrox Blox file on a network or any file sharing service for use by anyone other than yourself or allow anyone other than yourself to access it • May not copy the Wrox Blox file other than as allowed above • May not copy, redistribute, or modify any portion of the Wrox Blox contents in any way without prior permission from Wiley If you have any questions about these restrictions, you may contact Customer Care at (877) 762-2974 (8 a.m.–5 p.m. EST, Monday–Friday). If you have any issues related to Technical Support, please contact us at 800-762-2974 (United States only) or 317-572-3994 (International) 8 a.m.–8 p.m. EST, Monday–Friday). Acquisitions Editor Editorial Director Vice President and Executive Group Paul Reese Robyn B. Siesky Publisher Project Editor Editorial Manager Richard Swadley Kristin Vorce Mary Beth Wakefield Vice President and Executive Publisher Technical Editor Marketing Manager Barry Pruett Eilon Lipton David Mayhew Associate Publisher Senior Production Editor Production Manager Jim Minatel Debra Banninger Tim Tate Proofreader Copy Editor Nancy Carrasco Cate Caffrey