For today's blog post we are going to start simple and just
build a basic product catalog listing page like below:
Figure 1
This products.aspx page will take a Catalog index in the
URL, and use LINQ to SQL to retrieve and display product information. We
will also enable paging navigation at the bottom of the product listing (and do
all of the paging operations in the database - so that only 6 products at
a time are retrieved from the database).
The HTML markup output from the server will be 100% CSS
based (no tables or inline styles).
Step 1: Defining out Site Layout with Nested Master
Pages
Before we start working on our product page, we'll first
want to define the overall UI and layout structure for our site.
If you are "design challenged" like me, one
approach you might want to take with a new site is to get started by using
one of the free HTML site templates that you can download from these two
sites: http://www.opensourcetemplates.org/
or http://www.oswd.org/.
The templates on these sites are pure HTML (meaning you can use them with any
server-side programming technology), and are built using clean CSS and XHTML
markup. For this blog post I decided to go with the
"TerraFirma" template here.
After I downloaded the template, the first thing I did was
to create a root "Site.Master" Master Page that defined the overall
layout structure for the site. I then created a few nested master pages
to define different column style layouts ("SingleColumn.master" and
"TwoColumn.master"). VS 2008 now has great support for creating
and using nested master pages that makes doing this easy. You can read
about how to define and use them in my earlier VS 2008 Nested Master Page Support blog post.
Once we have created our master pages layouts, we can then
create a new product catalog page for the site that is based on the
single-column nested master page (click below for a full screen picture):
Figure 2
Notice above how we can edit the page using the new VS 2008
Split View mode feature. Above I'm using the "Vertical Split
View" option so that I can see both the source and design view on a
wide-screen monitor. You can learn how to enable this in my earlier Enabling Vertical Split View in VS 2008 blog post.
Step 2: Defining our CSS Rules using Mock HTML UI
When it comes to defining our product UI for the page, there
are a couple of different approaches we can use. One would be to start by
writing code to generate some dynamic data UI, and then work to make it
pretty. Another approach we could take would be to start by mocking up
the HTML UI first, and then once we are happy with it write the code to make it
dynamic. For this blog post I'm going to take this second approach.
To start let's just add a standard <ul><li> list
of product content into the page:
Figure 3
As you can see above, this <ul> list looks pretty
unattractive, and obviously not like what we want our product listing page to
look like. To make it more attractive, we'll use some of the new CSS
editing features I covered in my earlier VS 2008 Web Designer and CSS Support blog post.
Specifically we'll want to open the new "Manage
Styles" tool window in VS 2008 (you can open this by selecting the
Format->CSS Styles->Managed Styles menu item):
Figure 4
The manage styles window provides an easy way
for us to see all CSS rules currently in our CSS stylesheet. It also
enables us to quickly lookup CSS selector values, refactor css rules across
stylesheets, and create new rules.
We can create a new CSS rule for our product
listing by selecting the "New Style..." link in the "Manage
Styles" window. This will bring up a dialog that enables us to
choose where we want to define the CSS rule, and configure what settings we want
for it. For this sample we'll name the CSS selector ".productslist
li" and select the "Define in existing style sheet" option to
add it to the external stylesheet we already have for our application:
Figure 5
We can then hit "ok", and return back to source
mode to assign the CSS rule on our <ul> list (note how VS 2008 now
provides CSS intellisense in source view):
Figure 6
Currently our CSS rule doesn't have settings
assigned to it, so our <ul> list will still look the same as it did
before. We can change that by assigning some CSS settings.
There are a couple of ways we could set these
CSS settings: 1) open up the CSS stylesheet and set them in source mode, 2) use
the manage styles dialog we saw before to set them, or 3) use the new CSS
Properties Window to edit the CSS rules in real-time within the designer.
We can bring up the CSS Properties Windows via the View->CSS Properties
menu:
Figure 7
When you select an element either in
source-view or design-view, the CSS Properties Windows will list all of the CSS
rules that apply to it. The "Applied Rules" list at the top of
the CSS Properties window indicates the precedence order of cascading
rules. The CSS properties list below it then shows all of the setting
values assigned to that element.
The "target rule" drop down in the
style application toolbar (circled in red above) indicates which CSS
selector a change in the CSS Properties window will be assigned to. In
the example above our .productlist li rule is the CSS selector we currently
have selected - which means as we set values in the CSS Properties window they
will be persisted under that rule name in our external CSS stylesheet. No
style settings will be persisted inline the HTML page.
Let's now make some changes to our
"productlist li" CSS rule. First we'll change the layout
display mode to be "inline":
Figure 8
We'll then want to float each <li> to the left:
Figure 9
And lastly we'll set the bottom and left
margin of each <li> element to be 15px to space out the products nicely:
Figure 10
Notice how when we are done no inline styles
have been saved in our HTML page:
Figure 11
Instead they've all been cleanly saved under our
"productlist li" CSS selector in the external CSS stylesheet:
Figure 12
Now all that remains is for us to replace the static HTML
with some dynamic data coming from a database.
Step 3: Defining our LINQ to SQL Data Model
We'll use LINQ to SQL to retrieve our product data from the
database. LINQ to SQL is a great new ORM (object relational mapper)
implementation built into .NET 3.5. You can learn more about it from my
on-going LINQ to SQL blog series (more posts in it coming soon):
Part 1: Introduction to LINQ to SQL
Part 2: Defining our Data Model Classes
Part 3: Querying our Database
Part 4: Updating our Database
Part 5: Binding UI using the ASP:LinqDataSource Control
We'll use the Northwind sample database for our product data,
and define our data model classes in the LINQ to SQL ORM designer like so:
Figure 13
Once we have our LINQ to SQL data model defined, we are
ready to use the <asp:listview> control and bind the data to it.
Step 4: Convert our HTML Mock UI to use the
<asp:ListView> control
The <asp:listview> control is a template-driven
control. The control itself outputs no "built-in" UI, nor any
actual HTML markup. Instead, you can define whatever markup *you* want
displayed using the below templates:
·
LayoutTemplate
·
ItemTemplate
·
AlternatingItemTemplate
·
SelectedItemTemplate
·
EditItemTemplate
·
InsertItemTemplate
·
EmptyItemTemplate
·
EmptyDataTemplate
·
ItemSeparatorTemplate
·
GroupTemplate
·
GroupSeparatorTemplate
The first two templates in the list above - LayoutTemplate
and ItemTemplate - are the most common ones you'll end up using. The
<LayoutTemplate> template allows you to define the outer
container/wrapper of your data UI. The <ItemTemplate> template then
allows you to define what each item in the list should look like.
Within the <LayoutTemplate> you then define an
"ItemContainer" control that indicates where you want the
<asp:ListView> control to dynamically add the <ItemTemplate> items
into the output markup.
To see how this works in action, we could take our mock
products HTML UI:
Figure 14
And replace it with a <asp:listview>
that can dynamically generate the exact same markup output like so:
Figure 15
Notice above how I am using a
<asp:placeholder> control in the <LayoutTemplate> to indicate where
I want to add in my items in the list. I could use other controls
instead as the itemContainer if I wanted to - but by using an
<asp:placeholder> control as the itemContainer I will prevent any id
values or extra markup being generated.
Notice above how I've also defined an
<EmptyDataTemplate>. This will display instead of the
<LayoutTemplate> if I assign an empty sequence of data to the
ListView. This will avoid us inadvertently displaying an empty
<ul></ul> element in the event that there are no products in the
catalog specified.
Once we've defined our template above, we can
write some code in our code-behind file to retrieve our product data using LINQ
to SQL, and bind our ListView with it:
VB:
Figure 16
C#:
Figure 17
And now when we run the page and supply a valid categoryid
as a querystring argument we'll see our products dynamically pulled from the
database:
Figure 18
If we try a category that doesn't contain products, we'll
get our empty template message:
Figure 19
If you do a "view source" on the products page in
the browser, you'll see that the markup generated from our ListView control is
the same as what our static HTML was:
Figure 20
There are no ID elements or inline styles generated.
We had complete control over every HTML element and attribute generated.
Step 5: Using a <asp:LinqDataSource> control
instead of writing code
In our previous step above we wrote procedural LINQ to SQL
code to databind our <asp:ListView>. This obviously works, and
provides a great deal of control over the logic executed.
Another option you can alternatively use is a declarative
ASP.NET datasource control. All of the ASP.NET 2.0 datasource controls
(SqlDataSource, ObjectDataSource, AccessDataSource, XmlDataSource,
SiteMapDataSource, etc) work with the ListView. You can also use the new
<asp:LinqDataSource> control with it. For more information on the
LinqDataSource, check out my previous LINQ to SQL Part 5: Binding UI using the ASP:LinqDataSource
Control blog post.
To use the <asp:LinqDataSource> in the above sample,
we'll first delete the previous code we wrote in the code-behind file, and then
click on the <asp:ListView> control in the designer and select the
"Choose Data Source->New DataSource" option. We'll pick the
"LINQ DataSource" option in the data source dialog, and then bind to
the Northwind data model we created earlier.
We can then choose to bind the ListView against the
"Products" entity collection in our Northwind data model:
Figure 21
We can then hit the "Where" button
to configure a LINQ filter based on the "category" value in the
querystring (we could alternatively bind the value from a form value, cookie,
session value, another control, etc):
Figure 22
When we press the "ok" button the
ListView's DataSourceID will be set to a new <asp:LinqDataSource> in the
page:
Figure 23
And now without us having to have any code in the application
we have a product listing with custom HTML UI databinding against our LINQ to
SQL data model.
Step 6: Enabling Server Side Paging using the
<asp:DataPager> control
Our last step with this sample will be to enable paging
support over the products data. Specifically, we only want to display 6
products at a time on the page, and provide a nice page number UI to allow
users to navigate forward and backwards over our product sequence.
One of the other new controls in ASP.NET 3.5 is the
<asp:DataPager> control - which makes data paging scenarios with the
ListView control pretty easy. You can drop it anywhere you want on a
page, and set its "PagedControlID" property to point at the ListView,
and its "PageSize" property to indicate how many items in the ListView
you want displayed at a time:
Figure 24
The <asp:DataPager> will then output navigation UI for
your ListView:
Figure 25
And then if you click the "2" link in the paging
UI above it will show you the remaining 5 products in the category:
Figure 26
The <asp:LinqDataSource> automatically uses LINQ to
SQL's built-in support for server-side data paging to ensure that only 6
products (because the PageSize is 6) are ever retrieved from the database
at a time. This means that your application and database will scale
even if you have thousands of products in a specific category.
Disclaimer: The <asp:DataPager> in Beta2, though, does
have some limitations in that it can't by default be driven off of a
querystring value - which makes it not very SEO friendly. I'll cover how
to fix this and support a querystring index in a future blog post.