InData Entry and ASP.NET MVC
I discussed how to arrange a
data entry page using the
ASP.NET MVC Framework
. There s a common sentiment
around the community that the ASP.NET MVC Framework is not particularly suited
to data entry. I was one of those sustaining this point of view. Well, it lasted
until I tried it out seriously. Now I ll probably remain a fan of Web Forms for
most of the foreseeable future, but this is not a good reason to yield to
prejudice when it comes to ASP.NET MVC and data entry. Data entry (and input
validation as we ll see in this article) can be equally as effective in ASP.NET
MVC as it is in Web Forms. This said, feel free to pick up the framework with
which you feel most comfortable. But let s make it clear it is a matter of
preference.
Figure 1 shows where I left off last month. The figure
shows a Web page through which a user can select a customer from a list and
click to edit its record. When this happens, a new view is displayed that
includes a user control. The user control incorporates the user interface to edit
the content of the bound record. The user control also contains a Save button
and posts a new request to the site to update the record. Keeping the editing
stuff isolated in a user control makes it easy for you to replace it and
decouples to an acceptable level the host page from the edit-record engine. In this article, I ll further enhance the user control to
add the ability to display user messages that regard validation and other
errors. In doing so, I ll also be using the jQuery library to add some
animation. Since the advent of ASP.NET 1.0, I ve been working on
quite a few Web pages to do data entry. When it comes to this, you have
essentially two points to address: isolating the editing UI and update logic
from the page, and letting the page know about what happens during the editing
phase. By employing a user control for editing the record, you are
able to isolate the editing UI from the page. Furthermore, in an ASP.NET MVC
scenario, the logic to handle the update is already out of the page, isolated
in the controller. You must figure out how to pass data to the user control;
possibly in a way that abstracts the actual view away. In addition, you must
figure out how the user control communicates with the host page. This regards
how the input data is being passed to the user control, but also how messages
for errors and notifications are shown. Figure 2 shows structure and overall behavior of a sample
page using this pattern. When the user clicks to edit, a POST is made to the
Web server to turn the selected record into edit mode. The controller s Edit action is invoked for a POST verb: The parameter indicates the currently selected element in
the list. The action method simply redirects to the Edit action for a GET verb
the same action you expect to take place when an input/edit/id URL is
requested. This ensures a dual programming interface to the page the user gets
to the same point via both the user interface and the address bar. The Edit action for GET verbs performs two steps. First,
it refills the list and selects the element identified by the URL argument.
Second, it stores in the view s data container any data needed to update the
user interface. It is recommended that you opt for a custom data structure that
abstracts away the data required by the next view. However, a similar fragment
of code still works: The view will check the presence of data for the user
control and display it on demand, as shown here: Everything else takes place within the user control. The
user control renders its own view, drawing data from passed objects, and waits
for the user to enter data. Let s skip for just a moment over validation and
focus on the Save button. When the user clicks the Save button, a POST request
is generated to a URL input/update/id. The controller s Update action does the
following: The first step accomplished by the controller is updating the
record provided that valid data has been provided through the user interface.
The controller s method receives this data from the ASP.NET MVC machinery. In
turn, the ASP.NET MVC machinery gets raw data from the Request.Form collection
packed with the POST request (see last month s article for details). Finally, the controller sets any information for the user
interface and redirects to Edit mode. This is the pattern I found very
comfortable to use in a data entry page, and especially easy and effective in
ASP.NET MVC. Is there any reason to use a redirect in Update (and other
actions) instead of simply rendering the view? Using a redirect separates the
action of updating the model from the action of updating the view. They are two
distinct actions performed by the browser. The pattern PRG explains this
approach. PRG stands for Post/Redirect/Get. In this way, any page refresh is
done via GET, which makes for a cleaner MVC solution. However, it also saves
you from a nasty problem if the user refreshes (F5) the page displayed after,
say, an update, no POST is repeated and no message about resending data is displayed.
If you use the PRG pattern and redirect to a GET, any F5 refresh repeats a GET,
not a POST. Let s see more about updating the user interface to
reflect the state of the record during the update phase. It s fine to have the helper user control trigger the
update on the controller and redirect to a selected view. This is what one
would call a clean MVC solution. What about synchronizing the rest of the user
interface (mostly the page) with the effect of updates? Let me explain with an
example. Suppose you have a master/detail view. You edit the detail record
through the user control, but still you may need to update the master record to
possibly reflect changes. The user control can hardly take care of that. In
this case, the best you can do is instruct the user control to fire an event to
the host page. This the most general approach. However, in simpler but not necessarily infrequent situations,
you don t need to sync up the rest of the page with the actions performed
through the user control. Any feedback regarding the update in this case can be
displayed within the user control itself. In Figure 2, in fact, you see a
message bar area in the user control that displays the content set by the
controller. Here s how the controller stores data to display in the view: Why use TempData instead of ViewData? Any data you stuff
in ViewData is stored for the duration of the request. But if you redirect to
render the view, any such content is lost. TempData ensures that any data you
place in it also survives a second request, if this is a redirect. How would you display a message (be it an error or a
notification) after an update? If the update didn t work because of errors, you
want to keep the message on during editing for the user to keep in mind what
went wrong. If the message, instead, is a plain notification that the update
worked successfully, you might want to show it, but hide it at some point
because during an editing phase it doesn t make much sense to recall that the last
update worked just fine. In other words, what you want are flashy messages that
appear for just a few seconds if a text is found in TempData and disappear
shortly afterwards. You need client-side capabilities for this. Figure 3 shows
how to do it with jQuery. Figure 4 shows the real effect on the user interface.
The message Successfully updated! disappears in a couple of seconds, leaving
the editing interface clean for a new set of changes. Figure 3: A flashy
message using jQuery. You put the code in Figure 3 in the ASCX file of the user
control. The message is displayed when the page renders out. At the same time,
when the document is ready, a timer is set up that clears the message at the
end of the interval. You can use the TempData container to pass up to the user
interface any error message you want that relates to the update. The user
control receives an entity object from the model (in the sample code it s a
Customer object) and attempts to fit into that object any data the user entered
through input fields. But what if the input data is invalid? Clearly, any invalid data should originate an error
message that must be reported to the user. The best way to validate the state
of an object in an MVC context is a non-trivial problem with many aspects, and
deserves an article of its own. For the purpose of this article, I ll simply
assume you know how to validate an object and capture the list of invalid input
values and related error messages. How would you display these messages? In Web
Forms you have server-side validation controls that make it easy. A validation
control simply applies a built-in validation rule to a value, and has no vision
on the object that may be behind that scalar value. ASP.NET MVC provides some facilities to display error
messages associated with input data. You have a ValidationMessage helper to
generate a label and a ModelState collection to capture all error messages to
display. Let s start with the markup in the user control: ValidationMessage produces no output if the ModelState
collection on the ViewData object is empty. You add an entry to the collection
for each detected error. The controller s UpdateModel method uses the content
in the Form collection to update an entity object: Next, you validate the object applying some rules. For
each error detected, you add an entry to the ModelState: The preceding code is excerpted from a controller method.
However, this is a debatable point that deserves more attention. In ASP.NET MVC, the whole point is to fill the ModelState
collection. Once this is done, matching ValidationMessage elements will display
messages. The match is found on the key passed to ModelState and the argument
passed to ValidationMessage. If the page contains a CSS style named
.input_validation_error, that is automatically applied to the control with
wrong data, provided that the control is not styled already (see Figure 5). The approach described works great for HTML elements
incorporated in the Web page. If the input elements live within a user control
and the user control has a strongly typed view data model no information
about model state ever reaches the user control. As a result, no error message
is displayed. At least in Beta 1, you need to pass the content of ModelState to
TempData so the user control can retrieve it: The user control then copies the content of the passed
model state into its own model state: With this fix, it works also when the user interface is
isolated in a user control. Source code accompanying this article is available for download.
Figure 1: A sample data entry page.A Common Pattern for a Data Entry Page
Figure 2: Internal architecture of
an ASP.NET MVC data entry page.[ActionName("Edit"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditViaPost(string listCustomers)
{
string customerID = listCustomers;
return RedirectToAction("Edit",
new RouteValueDictionary(new { id = customerID }));
}
object data = GetContentForTheUserControl(...)
ViewData["CustomerData"] = data;
<%
object data = ViewData["CustomerData"];
if (data == null)
return;
Html.RenderPartial(userControlViewName, data);
%>
public ActionResult Update(string id)
{
// Update the data model (if current data is OK)
UpdateRecord(id);
// Save a message to UI
SetMessageBar();
// Redirect to Edit mode on the current record
RedirectToAction("Edit", new RouteValueDictionary(new
{ id = id }));
}
A Bit of jQuery in the User Control
// TempData is a dictionary on the controller base class
TempData["OutputMessage"] = "Successfully updated!";
<%
string msg = TempData["OutputMessage"] as string;
%>
Figure 4: The effect on the user
interface when using jQuery.Features for Validation
Country
<%= Html.TextBox("Country", ViewData.Model.Country,
new Dictionary UpdateModel
if (!cust.Country.Equals("Germany"))
{
ViewData.ModelState.AddModelError("Country",
"Invalid country.");
TempData["OutputMessage"] = "Check your input data.";
}
else
{
context.SubmitChanges();
TempData["OutputMessage"] = "Successfully updated!";
}
Figure 5: An example MVC application.Fix for User Controls
if (!cust.Country.Equals("Germany"))
{
ViewData.ModelState.AddModelError("Country",
"Invalid country.");
TempData["OutputMessage"] = "Check your input data.";
TempData["ModelState"] = ViewData.ModelState;
}
else { ... }
<%
if (TempData["ModelStateForUserControl"] != null)
{
foreach (var x in (ModelStateDictionary)TempData[
"ModelStateForUserControl"])
{
if (x.Value.Errors.Count <= 0)
continue;
string errorMsg = x.Value.Errors[0].ErrorMessage;
ViewData.ModelState.AddModelError(x.Key, errorMsg);
}
}
%>
Input Validation in ASP.NET MVC
Explore Model State and Data Validation
0 comments
Hide comments