|
ASP.NET MVC Framework (Part 4): Handling Form Edit and Post Scenarios
|
by Scott Guthrie
Feedback
|
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days):
57022/
93
|
|
|
Introduction |
Republished with Permission - Original Article
The last few weeks I have been working on a series of blog
posts that cover the new ASP.NET MVC Framework we are working on. The
ASP.NET MVC Framework is an optional approach you can use to structure your
ASP.NET web applications to have a clear separation of concerns, and make it
easier to unit test your code and support a TDD workflow.
The first post in this series built a simple e-commerce product
listing/browsing site. It covered the high-level concepts behind MVC, and
demonstrated how to create a new ASP.NET MVC project from scratch to implement
and test this e-commerce product listing functionality. The second post drilled deep into the URL routing architecture
of the ASP.NET MVC framework, and discussed both how it worked as well as how
you can handle more advanced URL routing scenarios with it. The third post discussed how Controllers interact with Views,
and specifically covered ways you can pass view data from a Controller to a
View in order to render a response back to a client.
In today's blog post I'm going to discuss approaches you can
use to handle form input and post scenarios using the MVC framework, as well as
talk about some of the HTML Helper extension methods you can use with it to
make data editing scenarios easier. Click
here to download the source code for the completed application we are going
to build below to explain these concepts.
|
Form Input and Post Scenario |
To help illustrate some of the basics of how to handle form
input and posting scenarios with the ASP.NET MVC Framework, we are going to
build a simple product listing, product creation, and product editing
scenario. It will have three core end-user experiences:
Product Listing By Category
Users will be able to see a listing of all products within a
particular product category by navigating to the
/Products/Category/[CategoryID] URL:
Figure 1
Add New Product
Users will be able to add a new product to the
store by clicking the "Add New Product" link above. This takes
them to the /Products/New URL - where they will be prompted to enter details
about a new product to add:
Figure 2
When they hit save, the product will be added
to the database, and they will be redirected back to the product listing page.
Edit Product
On the product listing page users can click
the "Edit" link next to each product. This takes them to the
/Products/Edit/[ProductID] URL - where they can change the product details and
hit the Save button in order to update them in the database:
Figure 3
Our Data Model
We are going to use the SQL Server Northwind Sample Database
to store our data. We'll then use the LINQ to SQL object relational
mapper (ORM) built-into .NET 3.5 to model the Product, Category, and Supplier
objects that represent rows in our database tables.
We'll begin by right-clicking on our /Models sub-folder in
our ASP.NET MVC project, and select "Add New Item" -> "LINQ
to SQL Classes" to bring up the LINQ to SQL ORM designer and model our data objects:
Figure 4
We'll then create a partial
NorthwindDataContext class in our project and add some helper methods to
it. We are defining these helper methods for two reasons: 1) it avoids us
embedding our LINQ queries directly in our Controller classes, and 2) it will
enable us to more easily adapt our Controller to use dependency injection in
the future.
The NorthwindDataContext helper methods we'll
add look like below:
Figure 5
To learn more about LINQ and LINQ to SQL, please check out
my LINQ to SQL series here.
|
Building Our ProductsController |
We are going to implement all three of our core end-user
browsing experiences using a single controller class - which we'll call
"ProductsController" (right click on the "Controllers" sub
folder and select "Add New Item" -> "MVC Controller" in
order to create it:
Figure 6
Our ProductsController class will handle URLs
like /Products/Category/3, /Products/New, and /Products/Edit/5 by implementing
"Category", "New", and "Edit" actions:
Figure 7
Read Part 1 and Part 2 of my ASP.NET MVC Series to learn more about how
these URLs are routed to the action methods on the ProductsController
class. For this sample we are going to use the default
/[Controller]/[Action]/[Id] route mapping rule - which means we do not need to
configure anything in order for the routing to happen.
Our Controller actions will be using three View pages in
order to render output. The "List.aspx", "New.aspx",
and "Edit.aspx" pages will live under the \Views\Products sub-folder,
and be based on the Site.Master master page under \Views\Shared.
|
Implementing Product Listing By Category |
The first part of the site that we'll implement will be the
Product Listing URL (/Products/Category/[CategoryId]):
Figure 8
We'll implement this functionality using the
"Category" action on our ProductsController class. We'll use
our LINQ to SQL DataContext class, and the GetCategoryById helper method we
added to it, to retrieve a Category object that represents the particular
category indicated by the URL (for example: /Products/Category/3). We'll
then pass the Category object to the "List" view to render a response
from it:
Figure 9
When implementing our List view we'll first update our
page's code-behind to derive from ViewPage<Category> so that our page's
ViewData property will be typed to the Category object that was passed by our
Controller (Part 3 discusses this more):
Figure 10
We'll then implement our List.aspx like below:
Figure 11
The above view renders the Category name at the top of the
page, and then renders a bulleted list of the Products within the
category.
Next to each product in the bulleted list is an
"Edit" link. We are using the Html.ActionLink helper method
that I discussed in Part 2 to render a HTML hyperlink (for example: <a
href="/Products/Edit/4">Edit</a>) that when pressed will navigate
the user to the "Edit" action. We are also then using the
Html.ActionLink helper method again at the bottom of the page to render a <a
href="/Products/New">Add New Product</a> link that when
pressed will navigate the user to the "New" action.
When we visit the /Products/Category/1 URL and do a
view-source in the browser, you'll notice that our ASP.NET MVC application has
emitted very clean HTML and URL markup:
Figure 12
|
Implementing Add New Product (Part 1 - Background) |
Let's now implement the "Add New Product" form
post functionality of our site. We'll ultimately want users to see a
screen like below when they visit the /Products/New URL:
Figure 13
Form input and editing scenarios are typically
handled in the ASP.NET MVC Framework by exposing two Action methods on a
Controller class. The first Controller Action method is responsible for
sending down the HTML containing the initial form to display. The second
Controller action method is then responsible for handling any form submissions
sent back from the browser.
For example, for our "Add Product"
screen above we could choose to implement it across two different
ProductsController actions: one called "New" and one called
"Create". The /Products/New URL would be responsible for displaying
an empty form with HTML textboxes and drop-down list controls to enter new
product details. The HTML <form> element on this page would then
have its "action" attribute set to the /Products/Create URL.
This means that when the user presses a form submit button on it, the form
inputs will be sent to the "Create" action to process and update the
database with.
Figure 14
|
Implementing Add New Product (Part 2 - First Approach) |
Below is an initial implementation that we could use for our
ProductsController.
Figure 15
Notice above that we now have two action
methods involved in the product creation process - "New" and
"Create". The "New" action method simply displays a
blank form to the user. The "Create" action method is what
processes the posted values from the form, creates a new Product in the
database based on them, and then redirects the client to the category listing
page for the product.
The HTML form sent to the client is
implemented in the "New.aspx" view called by the "New"
action method. An initial implementation of this (using textboxes for
everything) would look like below:
Figure 16
Note above how we are using a standard HTML <form>
element on the page (not a form runat=server). The form's
"action" attribute is set to post to our "Create" action
method on ProductsController. The post will happen when the <input
type="submit"> element at the bottom is pressed. When this
happens, the ASP.NET MVC Framework will automatically handle mapping the
ProductName, CategoryID, SupplierID and UnitPrice values as method parameters
to the "Create" action method on ProductsController:
Figure 17
And now when we run the site we have basic product entry
functionality working:
Figure 18
|
Implementing Add New Product (Part 3 - Using HTML Helpers
for Drop Down Lists) |
The product entry screen we created in the previous section
works, but isn't very user friendly. Specifically, it requires that the
end user know the raw CategoryID and SupplierID numbers for the Product being
entered. We need to fix this by instead displaying an HTML dropdownlist
that displays the human readable names.
Our first step will be to modify our ProductsController to
pass to the View two collections - one containing a list of available
categories, the other a list of available suppliers. We'll do this by
creating a strongly typed ProductsNewViewData class that encapsulates these,
and which we'll then pass to the View (you can learn about this in Part 3):
Figure 19
We'll then update our "New" action method to
populate these collections and pass them as the ViewData for the
"New" view:
Figure 20
Within our view we can then use these collections
to generate HTML <select> dropdowns.
|
ASP.NET MVC HTML Helpers |
One approach we could use to generate our
dropdowns would be to manually create our own <% %> for-loop containing
if/else statements within the HTML. This would give us total control over
the HTML - but would make the HTML messy.
A much cleaner approach that you can
alternatively use is to take advantage of the "Html" helper property
on the ViewPage base class. This is a convenient object that exposes a
set of HTML Helper UI methods that automate HTML UI generation. For
example, earlier in this post we used the Html.ActionLink helper method to
generate <a href=""> elements:
Figure 21
The HtmlHelper object (as well as the AjaxHelper object -
which we'll talk about in a later tutorial) have been specifically designed to
be easily extended using "Extension Methods" - which is a new language feature
of VB and C# in the VS 2008 release. What this means is that anyone can
create their own custom helper methods for these objects and share them for you
to use.
We'll have dozens of built-in HTML and AJAX helper methods
in future previews of the ASP.NET MVC Framework. In the first preview
release only the "ActionLink" method is built-into
System.Web.Extensions (the assembly where the core ASP.NET MVC framework is
currently implemented). We do, though, also have a separate
"MVCToolkit" download that you can add to your project to obtain
dozens more helper methods that you can use with the first preview
release.
To install the MVCToolkit HTML Helper methods, simply add
the MVCToolkit.dll assembly to your project references:
Figure 22
Re-build your project. And then the next time you type
<%= Html. %> you'll see many, many more additional UI helpers that you
can use:
Figure 23
To build our HTML <select> dropdowns we could use the
Html.Select() method. Each method comes with overloaded method versions -
all with full intellisense inside our views:
Figure 24
We could update our "New" view to use the
Html.Select options to display dropdownlists that use the CategoryID/SupplierID
properties as the values and CategoryName/SupplierName as the display text
using the code below:
Figure 25
This will generate the appropriate <select> HTML
markup for us at runtime:
Figure 26
And give end-users an easier way to pick the Product
Category and Supplier on our /Products/New screen:
Figure 27
Note: because we are still posting a CategoryID and
SupplierID value to the server, we do not need to update our ProductsController
Create Action at all to support this new drop-downlist UI - it will just work.
|
Implementing Add New Product (Part 4 - Cleaning Up Create
with the UpdateFrom Method) |
Our ProductsController's "Create" Action method is
responsible for handling the form posting of our "Add Product"
scenario. It currently handles incoming form parameters as
arguments to the action method:
Figure 28
This approach works fine - although for forms
involving large amounts of values the method signatures on Actions can start to
become a little hard to read. The code above that sets all of the
incoming parameter values to the new Product object is also a little long and monotonous.
If you reference the MVCToolkit assembly, you
can optionally take advantage of a useful Extension Method implemented within
the System.Web.Mvc.BindingHelpers namespace that can help clean this up a
little. It is called "UpdateFrom" and can be used on any .NET
object. It takes a dictionary of values as an argument, and it will then
automatically perform a property assignment on itself for any key that matches
a public property on the object.
For example, we could re-write our Create
action method above to use the UpdateFrom method like so:
Figure 29
Note: if you want to be more explicit for security reasons
and only allow certain properties to be updated, you can also optionally pass a
string array of the property names to update to the UpdateFrom method:
Figure 30
|
Implement Edit Product Functionality (Part 1 - Background) |
Let's now implement the "Edit Product"
functionality of our site. We'll ultimately want users to see a screen
like below when they visit the /Products/Edit/[ProductID] URL:
Figure 31
Like the "Add New Product" form post example
above, we are going to implement this form edit interaction using two
ProductsController Actions that we'll call "Edit" and
"Update":
Figure 32
"Edit" will display the product form.
"Update" will be used to handle the form's submit action.
|
Implement Edit Product Functionality (Part 2 - Edit Action) |
We'll begin enabling our application's Edit functionality by
implementing the ProductController's Edit action method. When we created
our product listing page at the beginning of this post, we built it so that the
Edit action will take an id argument as part of the URL (for example:
/Products/Edit/5):
Figure 33
We'll want the Edit action method to retrieve
the appropriate Product object from the database, as well as retrieve
collections of the available Suppliers and Categories (so that we can implement
dropdowns in our edit view). We'll define a strongly typed view object to
represent all of this using the ProductsEditViewData object below:
Figure 34
We can then implement our Edit action method
to populate this viewdata object and Render it with an "Edit" view:
Figure 35
|
Implement Edit Product Functionality (Part 2 - Edit View) |
We can implement the "Edit.aspx" view page using
the following approach:
Figure 36
Note how we are using both the Html.TextBox
and Html.Select helper methods in the sample above. Both of these are
extension methods from the MVCToolkit.dll assembly.
Notice how the Html.Select helper method has
an overloaded version that allows you to specify what the selected value is in
the dropdownlist. In the snippet below I'm indicating that I want the
Category drop down item to be automatically selected based on the edit
product's current CategoryID value:
Figure 37
Lastly - notice how we are using the
Url.Action() helper method to set the <form> element's "action"
attribute:
Figure 38
Both the Url.Action and Html.ActionLink helper methods use
the ASP.NET MVC Framework's Routing Engine to generate URLs (read Part 2 for details on how URL generation works). What
this means is that if we change the routing rules for Edits in our site, we
will not need to change any of the code in our Controller or View. For
example, we could re-map our URLs to use a more RESTful URL like /Products/1/Edit
instead of /Products/Edit/1, and have the above Controller and View continue to
work unmodified.
|
Implement Edit Product Functionality (Part 3 - Update
Action) |
Our last step will be to implement the "Update"
action method on our ProductController class:
Figure 39
Like our previous "Create" action method we'll
take advantage of the "UpdateFrom" extension method to automatically
populate our product object from the request. Notice that rather then
populate an empty product object though, we are using a pattern where we first
retrieve the old values from the database, then apply the changes the user made
to them, and then save them in the database.
Once the edit is made, we redirect back to the Product
Listing page - and automatically set the /Products/Category/[CategoryID] to
match the saved state of the Product we were working on.
|
Summary |
Hopefully this post has helped provide some more details
about how to handle Form Input and Post scenarios with the ASP.NET MVC
Framework, as well as provided some context for how you can handle and
structure common data entry and edit scenarios with it.
Click
here to download a .ZIP file that contains the source code for the
completed application we built above.
In future posts I'll cover how to handle validation and
error recovery situations with form input and editing scenarios. I'll talk
about some of the data and security scaffolding support built-in to enable
rapid application creation. I'll discuss how you can use ASP.NET AJAX to
perform AJAX-enabled edits using the MVC framework. And I'll drill deeper
into how you can unit test and add dependency injection to your Controllers.
Hope this helps,
Scott
|
Resources |
|
|
|
|
|
|
|