| This past Thursday the ASP.NET MVC feature team published a new "Preview 5" release of the ASP.NET MVC framework. You can download the new release here. This "Preview 5" release works with both .NET 3.5 and the recently released .NET 3.5 SP1. It can also now be used with both Visual Studio 2008 as well as (the free) Visual Web Developer 2008 Express SP1 edition (which now supports both class library and web application projects). Preview 5 includes a bunch of new features and refinements (these build on the additions in "Preview 4"). You can read detailed "Preview 5" release notes that cover changes/additions here. In this blog post I'm going to cover one of the biggest areas of focus with this release: form posting scenarios. You can download a completed version of the application I'll build below here. Basic Form Post with a Web MVC Pattern Let's look at a simple form post scenario - adding a new product to a products database: The page above is returned when a user navigates to the "/Products/Create" URL in our application. The HTML form markup for this page looks like below: The markup above is standard HTML. We have two <input type="text"/> textboxes within a <form> element. We then have an HTML submit button at the bottom of the form. When pressed it will cause the form it is nested within to post the form inputs to the server. The form will post the contents to the URL indicated by its "action" attribute - in this case "/Products/Save". Using the previous "Preview 4" release of ASP.NET we might have implemented the above scenario using a ProductsController class like below that implements two action methods - "Create" and "Save": The "Create" action method above is responsible for returning an html view that displays our initial empty form. The "Save" action method then handles the scenario when the form is posted back to the server. The ASP.NET MVC framework automatically maps the "ProductName" and "UnitPrice" form post values to the method parameters on the Save method with the same names. The Save action then uses LINQ to SQL to create a new Product object, assigns its ProductName and UnitPrice values with the values posted by the end-user, and then attempts to save the new product in the database. If the product is successfully saved, the user is redirected to a "/ProductsAdded" URL that will display a success message. If there is an error we redisplay our "Create" html view again so that the user can fix the issue and retry. We could then implement a "Create" HTML view template like below that would work with the above ProductsController to generate the appropriate HTML. Note below that we are using the Html.TextBox helper methods to generate the <input type="text"/> elements for us (and automatically populate their value from the appropriate property in our Product model object that we passed to the view): Form Post Improvements with Preview 5 The above code works with the previous "Preview 4" release, and continues to work fine with "Preview 5". The "Preview 5" release, though, adds several additional features that will allow us to make this scenario even better. These new features include: The ability to publish a single action URL and dispatch it differently depending on the HTTP Verb Model Binders that allow rich parameter objects to be constructed from form input values and passed to action methods Helper methods that enable incoming form input values to be mapped to existing model object instances within action methods Improved support for handling input and validation errors (for example: automatically highlighting bad fields and preserving end-user entered form values when the form is redisplayed to the user) I'll use the remainder of this blog post to drill into each of these scenarios. [AcceptVerbs] and [ActionName] attributes In our sample above we implemented our product add scenario across two action methods: "Create" and "Save". One motivation for partitioning the implementation like this is that it makes our Controller code cleaner and easier to read. The downside to using two actions in this scenario, though, is that we end up publishing two URLs from our site: "/Products/Create" and "/Products/Save". This gets problematic in scenarios where we need to redisplay the HTML form because of an input error - since the URL of the redisplayed form in the error scenario will end up being "/Products/Save" instead of "/Products/Create" (because "Save" that was the URL the form was posted to). If an end-user adds this redisplayed page to their browser favorites, or copy/pastes the URL and emails it to a friend, they will end up saving the wrong URL - and will likely have an error when they try and access it later. Publishing two URLs can also cause problems with some search engines if your site is crawled and they attempt to automatically traverse your action attributes. One way to work around these issues is to publish a single "/Products/Create" URL, and then have different server logic depending on whether it is a GET or POST request. One common approach used to-do this with other web MVC frameworks is to simply have a giant if/else statement within the action method and branch accordingly: The downside with the above approach, though, is that it can make the action implementation harder to read, as well as harder to test. ASP.NET MVC "Preview 5" now offers a better option to handle this scenario. You can create overloaded implementations of action methods, and use a new [AcceptVerbs] attribute to have ASP.NET MVC filter how they are dispatched. For example, below we can declare two Create action methods - one that will be called in GET scenarios, and one that will be called in POST scenarios: This approach avoids the need for giant "if/else" statement within your action methods, and enables a cleaner structuring of your action logic. It also eliminates the need to mock the Request object in order to test these two different scenarios. You can also optionally now use a new [ActionName] attribute to allow the method name implementation on your controller class to be different than that from the published URL. For example, if rather than having two overloaded Create methods in your controller you instead wanted to have the POST method be named "Save", you could apply the [ActionName] attribute to it like so: Above we have the same controller method signature (Create and Save) that we had in our initial form post sample. The difference, though, is that we are now publishing a single URL (/Products/Create) and are automatically varying the handling based on the incoming HTTP verb (and so are browser favorites and search engine friendly). Model Binders In our sample above the signature of the Controller action method that handles the form-post takes a String and a Decimal as method arguments. The action method then creates a new Product object, assigns these input values to it, and then attempts to insert it into the database: One of the new capabilities in "Preview 5" that can make this scenario cleaner is its "Model Binder" support. Model Binders provide a way for complex types to be de-serialized from the incoming HTTP input, and passed to a Controller action method as arguments. They also provide support for handling input exceptions, and make it easier to redisplay forms when errors occur (without requiring the end-user to have to re-enter all their data again - more on this later in this blog post). For example, using the model binder support we could re-factor the above action method to instead take a Product object as an argument like so: This makes the code a little more terse and clean. It also allows us to avoid having repetitive form-parsing code scattered across multiple controllers/actions (allowing us to maintain the DRY principle: "don't repeat yourself"). Registering Model Binders Model Binders in ASP.NET MVC are classes that implement the IModelBinder interface, and can be used to help manage the binding of types to input parameters. A model binder can be written to work against a specific object type, or can alternatively be used to handle a broad range of types. The IModelBinder interface allows you to unit test binders independent of the web-server or any specific controller implementation. Model Binders can be registered at 4 different levels within an ASP.NET MVC application, which enables a great deal of flexibility in how you use them: 1) ASP.NET MVC first looks for the presence of a model binder declared as a parameter attribute on an action method. For example, we could indicate that we wanted to use a hypothetical "Bind" binder by annotating our product parameter using an attribute like below (note how we are indicating that only two properties should be bound using a parameter on the attribute): Note: "Preview 5" doesn't have a built-in [Bind] attribute like above just yet (although we are considering adding it as a built-in feature of ASP.NET MVC in the future). However all of the framework infrastructure necessary to implement a [Bind] attribute like above is now implemented in preview 5. The open source MVCContrib project also has a DataBind attribute like above that you can use today. 2) If no binder attribute is present on the action parameter, ASP.NET MVC then looks for the presence of a binder registered as an attribute on the type of the parameter being passed to the action method. For example, we could register an explicit "ProductBinder" binder for our LINQ to SQL "Product" object by adding code like below to our Product partial class: 3) ASP.NET MVC also supports the ability to register binders at application startup using the ModelBinders.Binders collection. This is useful when you want to use a type written by a third party (that you can't annotate) or if you don't want to add a binder attribute annotation on your model object directly. The below code demonstrates how to register two type-specific binders at application startup in your global.asax: 4) In addition to registering type-specific global binders, you can use the ModelBinders.DefaultBinder property to register a default binder that will be used when a type-specific binder isn't found. Included in the MVCFutures assembly (which is currently referenced by default with the mvc preview builds) is a ComplexModelBinder implementation that uses reflection to set properties based on incoming form post names/values. You could register it to be used as the fallback for all complex types passed as Controller action arguments using the code below: Note: the MVC team plans to tweak the IModelBinder interface further for the next drop (they recently discovered a few scenarios that necessitate a few changes). So if you build a custom model binder with preview 5 expect to have to make a few tweaks when the next drop comes out (probably nothing too major - but just a heads up that we know a few arguments will change on its methods). UpdateModel and TryUpdateModel Methods The ModelBinder support above is great for scenarios where you want to instantiate new objects and pass them in as arguments to a controller action method. There are also scenarios, though, when you want to be able to bind input values to existing object instances that you own retrieving/creating yourself within the action method. For example, when enabling an edit scenario for an existing product in the database, you might want to use an ORM to retrieve an existing product instance from the database first within your action method, then bind the new input values to the retrieved product instance, and then save the changes back to the database. "Preview 5" adds two new methods on the Controller base class to help enable this - UpdateModel() and TryUpdateModel(). Both allow you to pass in an existing object instance as the first argument, and then as a second argument you pass in a security white-list of properties you want to update on them using the form post values. For example, below I'm retrieving a Product object using LINQ to SQL, and then using the UpdateModel method to update the product's name and price properties with form data. The UpdateModel methods will attempt to update all of the properties you list (even if there is an error on an early one in the list). If it encounters an error for a property (for example: you entered bogus string data for a UnitPrice property which is of type Decimal), it will store the exception object raised as well the original form posted value in a new "ModelState" collection added with "Preview 5". We'll cover this new ModelState collection in a little bit - but in a nutshell it provides an easy way for us to redisplay forms with the user-entered values automatically populated for them to fix when there is an error. After attempting to update all of the indicated properties, the UpdateModel method will raise an exception if any of them failed. The TryUpdateModel method works the same way - except that instead of raising an exception it wi |