samsalisbury.net

Tag: ASP.NET

Paginated ListView with ObjectDataSource minimal example

by on Aug.06, 2009, under ASP.NET, Minimal Examples

I’ve been wrestling with the ObjectDataSource control recently, and couldn’t find a minimal example of how it all hooks up, for simple, paged, read-only usage, anywhere. Therefore, I now present the paginated ListView with ObjectDataSource minimal example. Try to stay awake…

Introduction

In this minimal example, we will create an ASP.NET web form that contains a paginated (or “paged” in .Net parlance) ListView control, bound to an ObjectDataSource. In order to avoid any complex database access code mucking up the important part of this example, I’ll instead use the built-in collection of CultureInfo objects that come with the .Net Framework. So, the ‘business object’ collection we are displaying in the ListView will be a List<CultureInfo> (that’s a List(Of CultureInfo) for any VB.NET folk out there. Hopefully this will make for a semi-interesting tutorial…

Part 1: The markup

Of course, the first thing we will need is an aspx page containing the necessary controls for our minimal example — the important ones to include are ObjectDataSource, ListView and DataPager. Their opening tags are highlighted in the code below, note that the DataPager is inside the LayoutTemplate of the ListView. This doesn’t have to be the case, in which case you’d have to also provide the attribute PagedControlID to tell it which IPageableItemContainer compatible control to paginate.

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
	EnablePaging="true" OnSelecting="ObjectDataSource1_Selecting"
	TypeName="WebApplication1.MinimalObjectDataSourceObject"
	SelectMethod="MinimalSelectMethod" SelectCountMethod="MinimalSelectCountMethod" />

<asp:ListView ID="ListView1" runat="server" DataSourceID="ObjectDataSource1">
	<LayoutTemplate>Select page...
		<asp:DataPager ID="DataPager1" runat="server">
			<Fields><asp:NumericPagerField /></Fields>
		</asp:DataPager>
		<ul><li id="itemPlaceholder" runat="server" /></ul>
	</LayoutTemplate>
	<ItemTemplate>
		<li runat="server">
			<%#Eval("EnglishName") %> &mdash; <em><%#Eval("NativeName") %></em>
		</li>
	</ItemTemplate>
</asp:ListView>

We’ll refer back to this code later, important points to note for now are that

  • the ObjectDataSource has EnablePaging set to true and has its OnSelecting, TypeName, SelectMethod and SelectCountMethod attributes set, and
  • the ListView has its DataSourceID set to the ID of the ObjectDataSource.

Part 2: The enfunctioning

The ObjectDataSource control requires that you implement at least 2 functions (if you only want to READ data, as we are doing here). The first function, referred to as the SelectMethod, must return the IEnumerable of items for the page of data you want to display. The second, referred to as the SelectCountMethod should return the count of all the items in the data set. Not just the number on the first page! No no no! The number of items in total across all the pages! Yes yes yes! This often forces us to do some caching if we are to be efficient, however, we are dealing with an absolutely minimal example here, so I’ll leave that for a future article.

These methods must be public and can exist practically anywhere accessible to your ObjectDataSource. For the purposes of this article, they should also be static (that’s Shared for any VB.NET folk). In fact, this is not strictly necessary, but we are dealing with a simple, minimal example here, so we don’t need to worry about this for the moment.

SelectMethod (MinimalSelectMethod)
The SelectMethod must accept as arguments any parameters that you want to use to select your data (e.g. any search terms or filters you want applied), plus 2 more parameters: startRowIndex and maximumRows. These final two are used for paging. They must have the exact names I’ve used here, unless you want to specify custom names in the properties of the ObjectDataSource. Personally I’d just use the default names, if you’ve come this far it’s unlikely your class will be used to do anything else other than provide data to an ObjectDataSource.
SelectCountMethod (MinimalSelectCountMethod)
The SelectCountMethod needs to provide the total number of rows that should be returned by your query. Therefore, it also needs to be passed any parameters needed to filter the data. It doesn’t care about which page you want to view, so your custom parameters are all that’s needed.

So, here is a complete class exposing both of these methods (it also uses a helper method I’ve called “GetSomeKindOfList” to do the list filtering, it’s pretty simple so I won’t explain it here).

public class MinimalObjectDataSourceObject
{
	// A nice list for demonstration purposes.
	private static List<CultureInfo> baseList =
		new List<CultureInfo>(CultureInfo.GetCultures(CultureTypes.AllCultures));

	// Our minimal SelectMethod.
	public static List<CultureInfo> MinimalSelectMethod(
		string parameter1, string parameter2, int startRowIndex, int maximumRows)
	{
		List<CultureInfo> someList = GetSomeKindOfList(parameter1, parameter2);
		// Make sure we don't try to get objects that don't exist, ArgumentOutOfRangeException otherwise!
		if (startRowIndex + maximumRows > someList.Count)
		{ maximumRows = someList.Count - startRowIndex; }
		return someList.GetRange(startRowIndex, maximumRows);
	}

	// Our minimal SelectCountMethod.
	public static int MinimalSelectCountMethod(string parameter1, string parameter2)
	{
		return GetSomeKindOfList(parameter1, parameter2).Count;
	}

	// A method to get a filtered list for our primary data source.
	public static List<CultureInfo> GetSomeKindOfList(string parameter1, string parameter2)
	{
		return baseList.FindAll(x => x.EnglishName.ToLower().StartsWith(parameter1))
			.FindAll(x => string.IsNullOrEmpty(parameter2.ToLower()) ||
				x.EnglishName.ToLower().EndsWith(parameter2.ToLower()));
	}
}

So, as you might have guessed, lines 8 and 19 are the important ones here, containing our 2 methods. These methods can be called anything you like here, as long as the attributes in your ObjectDataSource tag are spelt exactly the same.

We’re almost there! Don’t have a baby yet though, the nasty bit’s coming…

Part 3: Pass the [parameter(s)]

Now we have the basic functions in place, we need to find some way to pass the parameters to the ObjectDataSource, so the results can be selected in any way that pleases us. This is the part that I found particularly difficult when implementing this for the first time, so pay attention!

The ObjectDataSource will automatically call its Selecting event, each time it needs to load a page of data for the ListView. It is here, and nowhere else, that we must programmatically set the parameters for our SelectMethod and our SelectCountMethod. This event receives an ObjectDataSourceSelectingEventArgs object as its second parameter, which in turn contains an IOrderedDictionary collection called InputParameters. So, you guessed it, we just need to set this up with the values we want to pass to our SelectMethod (and our SelectCountMethod). The names of the parameters must be exactly the same as the names we used in our definition of the SelectMethod and SelectCountMethod (the functions I’ve named MinimalSelectMethod and MinimalSelectCountMethod. We do this thusly…

protected void ObjectDataSource1_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
	e.InputParameters["parameter1"] = TextBox1.Text;
	e.InputParameters["parameter2"] = TextBox2.Text;
}

Remember the OnSelecting property of our ObjectDataSource tag in the aspx code from before? Well, this is the function whose name it must be set to. (Note, if you’re using VB.NET, then you don’t need to bother with defining the OnSelecting property in the aspx code, you just need a Sub that says Handles ObjectDataSource1.Selecting at the end of the first line of its definition. You also need to give it the same parameters as specified here.)

And that’s it, it should now work. Give yourself a big pat on the back.

Just one final note though. Since the Selecting event of the ObjectDataSource will be called each time a page is requested, the code I’ve used here could potentially cause an exception. “Really? Wow, how’s that?” I hear a little voice in my ear ask. Well, let’s go on a journey…

Daemons and devels

Actually, let’s not. Here’s why: the number of pages available when the DataPager was rendered may be different than the number of pages available after one of the page buttons is selected, in our example. This could happen for one of two reasons.

  • Firstly, the parameters may end up reducing the number of pages available, if they have been changed since the DataPager was rendered.
  • Secondly, the actual source dataset itself may have changed since the DataPager was rendered, meaning that any pages that are selected beyond the new number will not exist, and will therefore cause exceptions to be thrown.

I want to keep this article short, so I’ll simply outline a strategy to mitigate these potential exceptions. I’ve included a project in the accompanying download that has this implemented.

Avoiding exceptions

In my opinion, going to a different page in our dataset oughtn’t to be able to change the filter parameters, it just seems like unexpected behaviour to me. Therefore, as you’ll see in the attached project, one solution is to store the parameters’ current values separately from the controls’ current values, in the Session collection, only updating them when the ‘Search’ button is clicked.

This is the end

I’m going to try and draw a nice little diagram to illustrate the relationships between the pieces of code discussed, and post it here to serve as a quick reference. I’ll also upload the sample project very soon. Watch this space.

So, there you go. If you have anything to add, whinge about, or just want to give me a little ego boost (who wouldn’t?), then comments go in the box underneath. Ta.

11 Comments :, , , , , , , more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Archives

All entries, chronologically...