I must confess I am a bit jealous of many of the n00b developers who've just started
out using Visual Studio 2005 since it appeared late last year. They got started
with a whole new set of controls, many of which can be "hooked up"
to do incredibly productive things -- with no code at all. Meanwhile, we .NET
"Old Timers", having become used to doing things "our way",
have passed up on all this goodness in favor of the familiar. How many noob developers
know that the DataGrid in ASP.NET 2.0 is still there? Yep. It's just not on the
Toolbox by default. But, hey -- they've got the much better GridView, so what
do they care?
At any rate, there is a set of controls that newer developers seem to have gravitated
to, leaving the older developers like moi in the lurch - and that is the DataSource controls. One of those, the ObjectDataSource
control, I find quite interesting.
ObjectDataSource is a member of the family of ASP.NET data source controls that enable
declarative databinding against underlying data stores. Most data source controls
are designed for a two-tiered application architecture, where the page interacts
directly with the data provider. Many ASP.NET developers want to encapsulate
data retrieval, often combined with business logic, into a tier component object
that introduces an additional layer between the presentation page and data provider.
ObjectDataSource allows developers to structure applications using this three-tiered
architecture and still take advantage of the benefits of declarative databinding
in ASP.NET. It's not a full-fledged Object Relational Mapping infrastructure,
but it is a step in the right direction, with familiar semantics that make it
highly useful. Old-timers will probably remember "ObjectSpaces" and
how it went through several iterations and finally was killed off (quite unceremoniously,
I believe).
The ObjectDataSource control object model is similar to the SqlDataSource control,
but instead of a ConnectionString property, ObjectDataSource exposes a TypeName property that specifies a type to instantiate for performing the data operations.
Like the command properties of SqlDataSource, the ObjectDataSource control supports
properties such as SelectMethod, UpdateMethod, InsertMethod, and DeleteMethod for specifying methods of the associated type to call to perform these data operations.
The difference is that instead of being SqlCommands (SelectCommand, for example)
the methods are actual methods of the class that was specified for the ObjectDataSource
control to use. This gives you, the developer, the opportunity to do a lot more
business logic coding in your type, while still offering the familiar page-facing
databinding interface that GridView, DataList and their brethren can consume
happily. ObjectDataSource performs all this magic through reflection- hence it
is wise to create a target class that has static methods in order to avoid constant
and repetitive instantiation and collection every time a method is used.
The advantages of this approach haven't been lost on .NET Developers -- many code
generators are now producing code for Data layers that is ObjectDataSource -
compliant. One example that I like is the "SubSonic Zero Code DAL" (formerly called "ActionPack"). I started out creating a SQLite
Provider for it, but I had to stop because their code is still a moving target.
Once it settles down, I'll resume my work.
I first became interested in the ObjectDataSource control when I visited Peter Kellner's code for the MembershipProvider. Peter has done a bang-up job of integrating this into
an ASP.NET "Membership Management" page - a version of which I featured
in a previous article here.
So let's take a look at some sample code that utilizes the ObjectDataSource. I'll
keep this example simple in keeping with the "BASICS" theme of these
articles. What I'm going to do is illustrate the flexibility of the ObjectDataSource
by not having it get data from a database at all. Instead, our type will go out
to the web and retrieve search result feeds from Live.com feed search in RSS
format. These will then be databound to a GridView on the page. I'll also put
in a "search" textbox and the required code and declarative markup
to enable the control to use the search term that the user enters as a "select
parameter" in the same way one would add a search term to the "WHERE
CLAUSE" of a SQL statement.
First, lets have a look at the two classes I use to handle the "data access"-
the RSSFeed class, and the RSSDataSource class:
RSS Feed Class:
using System;
using System.Collections.Generic;
using System.Text;
namespace RSSObjectDataSource
{
public class RSSFeed
{
private string _title;
public string Title
{
get { return _title; }
set { _title = value; }
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value; }
}
private string _link;
public string Link
{
get { return _link; }
set { _link = value; }
}
private DateTime _pubDate;
public DateTime PubDate
{
get { return _pubDate; }
set { _pubDate = value; }
}
public RSSFeed(string title, string description, string link, DateTime pubDate)
{
this._title = title;
this._description = description;
this._link = link;
this._pubDate = pubDate;
}
}
}
The RSSFeed class, as can be seen above, is just a simplified "container"
for an RSS Feed Item, and it only contains the most important fields (title,
description, link, and pubDate).
The RSS DataSource class:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Data;
namespace RSSObjectDataSource
{
public static class RSSDataSource
{
public static List<RSSFeed> FeedList = new List<RSSFeed>();
public static string LastSearchTerm = "";
private static string url1 = "http://search.live.com/feeds/results.aspx?q=";
private static string url2 = "&mkt=en-US&format=rss&count=50"; // sorry they don't go over "50".
public static int GetCount( string searchTerm)
{
return FeedList.Count;
}
public static ICollection<RSSFeed> GetFeeds( string searchTerm)
{
LastSearchTerm = searchTerm;
DataSet ds = new DataSet();
if (searchTerm == "") searchTerm = "a";
ds.ReadXml(string.Concat(url1, searchTerm, url2));
DataTable dt = ds.Tables[2];
List<RSSFeed> list= new List<RSSFeed>();
foreach (DataRow row in dt.Rows)
{
string title = (string)row["title"];
string description =(string)row["description"];
string link = (string)row["link"];
DateTime pubDate =DateTime.Now;
// handle malformed RFC822 date formats
try{
pubDate =Convert.ToDateTime(row["pubDate"]);
}
catch {}
list.Add(new RSSFeed(title, description, link, pubDate));
}
FeedList = list;
return list;
}
public static ICollection<RSSFeed> GetFeeds(string searchTerm,
int maxRows, int startRowIndex)
{
DataSet ds = new DataSet();
if (searchTerm == "") searchTerm = "a";
ds.ReadXml(string.Concat(url1, searchTerm, url2));
DataTable dt = ds.Tables[2];
List<RSSFeed> list = new List<RSSFeed>();
foreach (DataRow row in dt.Rows)
{
string title = (string)row["title"];
string description = (string)row["description"];
string link = (string)row["link"];
DateTime pubDate = DateTime.Now;
try
{
pubDate = Convert.ToDateTime(row["pubDate"]);
}
catch { }
list.Add(new RSSFeed(title, description, link, pubDate));
}
FeedList = list;
if (list.Count < maxRows) maxRows = list.Count;
List<RSSFeed> retList = new List<RSSFeed>();
for (int i=startRowIndex;i<maxRows;i++)
{
retList.Add(list[i]);
}
return retList;
}
}
}
RSSDataSource provides the "guts" of the mechanism, and exposes methods
that are designed specifically for the ObjectDataSource control to be able to
consume.
Now let's take a look at some declarative markup on the page that pulls this all
together:
<form id="form1" runat="server">
<div>
<asp:TextBox
ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button
ID="Button1" runat="server" OnClick="Button1_Click"
Text="Search" /></div>
<asp:GridView
ID="_rssGrid" runat="server" AllowPaging="True"
AutoGenerateColumns="False"
DataSourceID="_rssObjectDataSource"
DataKeyNames="pubDate"
CellPadding="4" ForeColor="#333333" GridLines="None"
Width="802px"
PageSize="50"
RowHeaderColumn="pubDate" ShowFooter="True">
<Columns>
<asp:HyperLinkField
DataNavigateUrlFields="link" DataTextField="title" Text="Title"
/>
<asp:BoundField
HeaderText="pubDate" DataField="pubDate"
SortExpression="pubDate" />
<asp:BoundField
DataField="description" HeaderText="description" SortExpression="description"
/>
</Columns>
<FooterStyle
BackColor="#990000" Font-Bold="True" ForeColor="White"
/>
<RowStyle
BackColor="#FFFBD6" ForeColor="#333333" />
<SelectedRowStyle
BackColor="#FFCC66" Font-Bold="True" ForeColor="Navy"
/>
<PagerStyle
BackColor="#FFCC66" ForeColor="#333333" HorizontalAlign="Center"
BorderStyle="Solid" BorderWidth="1px"
/>
<HeaderStyle
BackColor="#990000" Font-Bold="True" ForeColor="White"
/>
<AlternatingRowStyle
BackColor="White" />
</asp:GridView>
<asp:ObjectDataSource
ID="_rssObjectDataSource" runat="server" SelectMethod="GetFeeds"
TypeName="RSSObjectDataSource.RSSDataSource" CacheDuration
="21600"
EnablePaging="True"
MaximumRowsParameterName="maxRows" SelectCountMethod="GetCount"
OnSelecting="_rssObjectDataSource_Selecting">
<SelectParameters>
<asp:ControlParameter
ControlID="TextBox1" DefaultValue="a"
Name="searchTerm"
PropertyName="Text"
Type="String"
/>
</SelectParameters>
</asp:ObjectDataSource>
</form>
The GridView here specifies the pubDate as the DataKey ("Identity" field),
a hyperlink field for the title and link, and bound fields for the pubDate and
description items.
The ObjectDataSource specifies "GetFeeds" as it's select method, the TypeName
of my RSSDataSource class, a Cache Duration of 21600 (6 hours - caching is built
in),
and a SelectParameter of the TextBox for the search term. The interesting thing about
all this is that if you look at the codebehind class for this page, there is
NO CODE! It all works automatically based on the settings of the control.
Here is an example display (reduced size) where the user has typed "ASP.NET"
into the search textbox. This is a nice way to see search results that point
directly to RSS Feeds, and when you click on a link that you like, you can subscribe
to the feed immediately in IE 7 or Firefox 2.0: