In this week's Preview 5 drop the UpdateModel methods always
work against the Request object's Form post collection to retrieve
values. This means that to test the above form post action method you'd
need to mock the request object in your unit test.
With the next MVC drop we'll also add an overloaded
UpdateModel method that allows you to pass in your own collection of values to
use instead. For example, we would be able to use the new FormCollection
type in preview 5 (which has a ModelBuilder that automatically populates it
with all form post values) and pass it to the UpdateModel method as an argument
like so:
Using an approach like above will allow us to unit test our
form-post scenario without having to use any mocking. Below is an example unit
test we could write that tests that a POST scenario successfully updates with
new values and redirects back to the GET version of our action method.
Notice that we do not need to mock anything (nor do we have to rely on any
special helper methods) in order to unit test all the functionality in our
controller:
Handling Error Scenarios - Redisplaying Forms with
Error Messages
One of the important things to take care of when handling
form post scenarios are error conditions. These includes cases where an
end-user posts incorrect input (for example: a string instead of a number for a
Decimal unit-price), as well as cases where the input format is valid, but the
business rules behind the application disallow something from being
created/updated/saved (for example: making a new order for a discontinued
product).
If a user makes a mistake when filling out a form, the form
should be redisplayed with informative error messages that guide them towards
fixing it. The form should also preserve the input data the user
originally entered - so that they don't have to refill this manually.
This process should repeat as many times as necessary until the form
successfully completes.
Basic Form Entry Error Handling and Input Validation
with ASP.NET MVC
In our product edit sample above we haven't written much
error handling code in either our Controller or our View. Our Edit post
action simply wraps a try/catch error handling block around the UpdateModel()
input mapping call, as well as the database save SubmitChanges() call. If
an error occurs, the controller saves an output message in the TempData
collection, and then returns our edit view to be redisplayed:
With earlier preview releases of ASP.NET MVC the above code
wouldn't be enough to deliver a good end-user experience (since it wouldn't
highlight the problem, nor preserve user input if there was an error).
However, with "Preview 5" you'll find that you now
get a decent end-user error experience out of the box with just the above
code. Specifically, you'll find that when our edit view is redisplayed
because of an input error it now highlights all input controls that have
problems, and preserves their input for us to fix:
How, you might ask, did the Unit Price textbox
highlight itself in red and know to output the originally entered user value?
"Preview 5" introduces a new
"ModelState" collection that is passed as part of the
"ViewData" sent from the Controller to the View when it
renders. The ModelState collection provides a way for Controllers to
indicate that an error exists with a model object or model property being
passed to the View, and allows a human friendly error message to be specified
that describes the issue, as well as the original value entered by the
end-user.
Developers can explicitly write code to add
items into the ModelState collection within their Controller actions.
ASP.NET MVC's ModelBinders and UpdateModel() helper methods also automatically
populate this collection by default when they encounter input errors. Because
we were using the UpdateModel() helper method in our Edit action above, when it
failed in its attempt to map the UnitPrice TextBox's "gfgff23.02"
input to the Product.UnitPrice property (which is of type Decimal) it
automatically added an entry to the ModelState collection.
Html helper methods inside the View by default
now check the ModelState collection when rendering output. If an error
for an item they are rendering exists, they will now render the originally
entered user value as well as a CSS error class to the HTML input
element. For example, for our "Edit" View above we are using
the Html.TextBox() helper method to render the UnitPrice of our Product object:
When the view was rendered during the error
scenario above the Html.TextBox() method checked the ViewData.ModelState
collection to see if there were any issues with the "UnitPrice"
property of our Product object, and when it saw that there was rendered the
originally entered user input ("gfgff23.02") and added a css class to
the <input type="textbox"/> it output:
You can customize the appearance of the the
error css classes to look however you want. The default CSS error rule
for input elements in the stylesheet created in new ASP.NET MVC projects looks
like below:
Adding Additional Validation Messages
The built-in HTML form helpers provide basic
visual identification of input fields with issues. Let's now add some
more descriptive error messages to the page as well. To-do this we can
use the new Html.ValidationMessage() helper method in "Preview
5". This method will output the error message in the ModelState
collection that is associated with a given Model or Model property.
For example: we could update our view to use
the Html.ValidationMessage() helper to the right of the textboxes like so:
Now when the page renders with an error, an
error message will be displayed next to the fields with problems:
There is an overloaded version of the
Html.ValidationMessage() method that takes a second parameter that allows the
view to specify an alternative text to display:
One common use case is to output the * character next to the
input fields, and then add the new Html.ValidationSummary() helper method (new
in "Preview 5") near the top of the page to list all the error
messages:
The Html.ValidationSummary() helper method will then render
a <ul><li></ul> list of all the error messages our ModelState
collection, and a * and red border will indicate each input element that has a
problem:
Note that we haven't had to change our ProductsController
class at all to achieve this.
Supporting Business Rules Validation
Supporting input validation scenarios like above is useful,
but rarely sufficient for most applications. In most scenarios you also
want to be able to enforce business rules, and have your application UI cleanly
integrate with them.
ASP.NET MVC supports any data layer abstraction (both ORM
and non-ORM based), and allows you to structure your domain model, as well as
associated rules/constraints, however you want. Capabilities like Model
Binders, the UpdateModel helper method, and all of the error display and
validation message support are explicitly designed so that you can use whatever
preferred data access story you want within your MVC applications (including
LINQ to SQL, LINQ to Entities, NHibernate, SubSonic, CSLA.NET, LLBLGen Pro,
WilsonORMapper, DataSets, ActiveRecord, and any other).