Skip navigation

Data Entry and ASP.NET MVC

<br />(March 2009 Issue)

Although still in a beta stage, the ASP.NET MVC Framework is definitely being talked about a lot. Indeed, quite a few people are already using it while contributing to shape its feature set and characteristics. The ASP.NET MVC Framework heralds a brand new approach to ASP.NET development that is radically opposed to classic Web Forms programming, and, to a large extent, is even incompatible with it. For more information, see "Understanding the ASP.NET MVC Framework " and " Input Validation in ASP.NET MVC. "

Picking up Web Forms or the ASP.NET MVC Framework is mostly a matter of preference and attitude, even though quite a few things come definitely better and easier with the ASP.NET MVC Framework. The most obvious example is unit testing. Another aspect that is often mentioned as superior in the ASP.NET MVC Framework is separation of concerns (SoC). However, while the ASP.NET MVC Framework is designed with SoC in mind, adding SoC and a certain degree of testability to classic ASP.NET is certainly not impossible. The Web Client Software Factory from Microsoft s Patterns and Practices group shows just that.

However, the ASP.NET MVC Framework also is not perfect. Overall, I d say that it really shines when all you need to do is display data. The structure of the ASP.NET MVC Framework is essentially RESTful, and allows you to do a lot of things through URLs. But data display is not all you need to do on a Web site. Data entry is another fundamental block. Admittedly, data entry with Web Forms is definitely more immediate and maybe more intuitive to set up compared to the MVC Framework. The main reason for this is probably to be found in the fact that, by design, Web Forms tries to abstract Web programming into a Windows-like and RAD programming environment.

In this article, I ll go through an example of form-based data entry using the ASP.NET MVC Framework. You ll see that data entry can be equally powerful with ASP.NET MVC, and can result in even more elegant coding, but requires a different model and mindset.

 

Data Entry in ASP.NET

Classic ASP.NET bases its programming model on the assumption that state is maintained across postbacks. This is not true at all at the HTTP protocol level, but it is brilliantly simulated using the feature of viewstate and a bit of work in the page lifecycle. The ASP.NET MVC Framework simply uses a different pattern that is not page-based. Does this mean more productivity? Or assure less productivity? It actually depends on your preferences and attitude.

If you ve grown up with Web Forms and its server controls, you may be shocked when transported into the MVC model. If you re open-minded, or are coming from a non-ASP.NET world, then it will definitely be love at first sight. After the first builds of the ASP.NET MVC Framework, many people, quite reasonably, complained about data entry in the new framework. Today, things are going to be significantly different.

Data entry is a scenario where server controls really shine, and their postback and viewstate overhead saves you from a lot of work. Server controls also give you a powerful infrastructure for input validation.

In the latest builds of the ASP.NET MVC Framework, you must take care of the design and configuration of the forms but if you carefully name input elements, you benefit from a number of facilities that fall under the umbrella of the Convention over Configuration paradigm (CoC). Originated in Ruby on Rails, this paradigm is becoming popular in the .NET space, thanks to the ASP.NET MVC Framework. Essentially, CoC means coding by convention in order to reduce the points of decision in a class gaining in simplicity, while maintaining a good deal of flexibility. Among other things, conventions in the ASP.NET MVC Framework data entry mean that the framework can automatically map input data from the HTTP packet down to the model. Let s explore and learn more details.

 

Form Posting

The sample application I ll discuss allows users to pick a customer from a dropdown list, then display an edit form where updates can be entered and saved. The application is based on three actions:

  • list the customers
  • edit a particular customer
  • save changes for a particular customer

The model is built with LINQ to SQL, and consists of the sole Northwind s Customers table. Figure 1 shows the controller s action that populates the dropdown list to offer the first screen to the user.

public ActionResult List()

{

 // Get the data to populate the list of customers

 using(NorthwindDataContext context = new NorthwindDataContext())

 {

   var data = (from c in context.Customers

                select new { c.CustomerID, c.CompanyName }).ToList();

   ViewData["listCustomers"] = new SelectList(data, "CustomerID", "CompanyName");

 }

 return View("List");

}

Figure 1: A List action.

 

By clicking a submit button after selecting a customer from the list, the user submits a POST request for an Edit action on the InputController class:

<% Html.BeginForm("Edit", "Input"); %>

<%= Html.DropDownList("listCustomers",

 ViewData["listCustomers"] as SelectList)%>



<% Html.EndForm(); %> 

In Figure 2 you can see two implementations of the Edit action, one for a GET request and one for a POST request. The AcceptVerbs attribute specifies the supported verb, whereas the AcceptName attribute breaks the automatism based on which the name of the method determines the action. In this way, you can have multiple implementations for the same action that you use in different scenarios. Because a dropdown list is used to select the customer in the user interface, you can hardly associate beforehand the submit button with the correct URL.

[ActionName("Edit"), AcceptVerbs(HttpVerbs.Get)]

public ActionResult EditViaGet(string id)

{

 PrepareViewData(id);

 return View("Edit");

}

[ActionName("Edit"), AcceptVerbs(HttpVerbs.Post)]

public ActionResult EditViaPost(string listCustomers)

{

 // This runs when the user posts from the List page.

 string customerID = listCustomers;

 return RedirectToAction("Edit",

   new RouteValueDictionary(new { id = customerID }));

}

Figure 2: Distinct methods for GET/POST edit actions.

The URL would be something like /input/edit/id, but you just don t know at design time which customer ID will actually be selected at run time. For this reason, you need a GET method that resolves direct queries for URLs like /input/edit/id; at the same time, you also need a POST method that figures out the customer ID from the Request object. The signature of the EditViaPost method in Figure 2 only shows the sense of CoC. If the parameter name matches an element in the Request.Form collection, then that value will be passed to the method. Needless to say, using Request.Form directly also would work:

string customerID = Request.Form["listCustomers"] as string; 

The EditViaPost method redirects to the GET handler so you can have a single edit point and a single URL to enter in edit mode (see Figure 3).


Figure 3: Editing a customer s record.

The edit view contains an optional user control used for actual data entry. The user control is displayed only if the ViewData container includes data for it:

<%

   object data = ViewData["CustomerData"];

   if (data == null)

       return;

   Html.RenderPartial("CustomerEdit", data);

%> 

As you can see, the Save button in the user control points to /input/update/alfki. This is the result of the following code in the CustomerEdit.ascx user control:

<% Html.BeginForm("Update", "Input", new

 { id = ViewData.Model.CustomerID }); %> 

Nothing has been done so far that is relevant to data entry. The core of the data entry stuff is in the user control.

 

Collecting Posted Data

In Figure 4 you can see the structure of the input form through which the user can edit the selected customer s record.

ID <%= ViewData.Model.CustomerID %>
Company <%= Html.TextBox("CompanyName", ViewData.Model.CompanyName, new Dictionary() { { "class", "textBox" } }) %>
Contact <%= Html.TextBox("ContactName", ViewData.Model.ContactName, new Dictionary() { { "class", "textBox" } }) %>

Figure 4: A sample input form.

The code-behind class of the user control utilizes a strongly-typed model, as in this code snippet:

public partial class CustomerEdit :

   System.Web.Mvc.ViewUserControl

{

}

Through the properties exposed out of the ViewData.Model object, you can then populate the various input fields of the user control. To add proper style information, use a dictionary of HTML attributes. To take advantage of the CoC support in the ASP.NET MVC Framework, you might want to name text boxes after properties in the Customer type. For example, a TextBox element named CompanyName automatically will be associated to the CompanyName parameter on the Update action within the controller. At display time, the binding is determined by a hard-coded value:

<%= Html.TextBox("CompanyName", ViewData.Model.CompanyName,

 new Dictionary() { { "class",

 "textBox" } }) %>

Once the form is posted, the ASP.NET MVC Framework will use matching names to associate the value entered in an input field to a corresponding parameter in the action method signature:

public ActionResult Update(string customerID,

 string companyName, string contactName, ...) 

However, if you don t like such a long signature, you can opt for the following:

public ActionResult Update(string id, FormCollection form)

FormCollection is simply an abstraction over Request.Form that, among other things, makes testing easier. The form collection contains as many elements as there are in the low-level ASP.NET Form collection. How can you update the model with posted data? An obvious answer is the following code:

Customer cust = from c in context.Customers

   where c.CustomerID==id ...;

cust.CompanyName = Request.Form["txtCompany"]; 

This code works regardless of the name you assign to the input fields. For testing purposes, you might want to make Request.Form disappear from the controller s code. If you opted for CoC and a signature that lists one parameter for each input field, you can do the following:

// Variable companyName is a method's argument

cust.CompanyName = companyName;  

Finally, if you opted for a FormCollection in the method s signature, here s the code that works for you:

cust.CompanyName = form["CompanyName"];  

In most recent builds, though, Microsoft introduced the UpdateModel method on the Controller class. This method accesses the Request.Form collection (or any collection you specify) and updates the provided object. The resulting code for the Update action is significantly simpler and easier to read:

public ActionResult Update(string id)

{

   Customer cust = from c in context.Customers

      where c.CustomerID==id ...;

   UpdateModel(cust);

   context.SubmitChanges();

   :

}

One thing is left: How can you update the user interface to reflect a successful update?

 

Updating the User Interface

The user interface of Figure 3 contains the details of the current record, as well as the list to pick up a new one. After the update is made, though, you want to show a message to the user but, at the same time, you don t want to navigate to another view just to show a message. And you don t want to maintain two distinct views that basically only differ for a label. Subsequently, the obvious solution is having the Update method redirect to the Edit action, as shown here:

return RedirectToAction("Edit",

 new RouteValueDictionary(new { id = id }));

In this way, you are now displaying the same page as in Figure 3, plus a message that reflects the success or failure of the update operation. The edit view will contain a simple tag for the message; but how are you passing data from the Update method to the Edit view? You can t pass custom data through the RedirectToAction or View methods. However, you can use the TempData container:

public ActionResult Update(string id)

{

   :

   TempData["OutputMessage"] = "Successfully updated!";

   return RedirectToAction("Edit",

     new RouteValueDictionary(new { id = id }));

}

 

In the Edit view, you have the following:



  <%= TempData["OutputMessage"] %>

 

Does this exhaust the data entry topic? Not at all. Error handling and validation are two strongly related topics that deserve an article of their own. Next month I ll pick up from here to introduce the topic of validation in an MVC model and discuss a bit of jQuery to animate messages.

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish