Exploring ASP.NET with the IBuySpy Case Studiesby Matthew MacDonald, coauthor of ASP.NET in a Nutshell
Developers have been listening to the hype about ASP.NET -- Microsoft's next generation Web application platform -- for almost a year now. If you're a programmer at a Microsoft shop, or an old-hand ASP coder, you've probably already explored the new platform, and may be planning to build a new ASP.NET solution or migrate an existing ASP site into the world of .NET. Features like class-based design, rich Web controls, integrated caching, and a powerful new data access model are hard to resist.
Of course, now that ASP.NET development is underway in earnest, there are a whole host of new considerations for .NET developers. Many have only recently digested the features of the new platform, and are still wondering how to fit them together with the best design practices and techniques that will guarantee scalability, reliability, and maintainability. Unfortunately, there's no way to gain this knowledge without some painful experience -- or is there?
The IBuySpy Case Studies
One of Microsoft's best-kept secrets is IBuySpy, a set of two case studies that demonstrate complete ASP.NET solutions. The IBuySpy store presents an e-commerce shop that includes a shopping basket, custom comments, and account management. The IBuySpy portal presents a completely configurable multi-tabbed portal site assembled out of distinct modules. But more interesting for the developers are the architectural decisions Microsoft has made.
You can run the case studies online by surfing to www.ibuyspystore.com (for the IBuySpy e-commerce storefront) or www.ibuyspyportal.com (for the portal). These sites are fully functional, and include links that let you peruse the code online. But best of all, you can download both of these case studies to explore on your own Web server or workstation from www.ibuyspy.com.
The case studies are available in four versions, depending on the language you want to use (C# or VB) and the environment (inline code ala Notepad or Visual Studio .NET). The setup program automatically creates the required virtual directory, solution files, and database (provided you are using SQL Server).
The IBuySpy Insight
IBuySpy is similar to other Microsoft case studies (like Duwamish and Fitch & Mather Stocks), but because it was created from the ground up for ASP.NET, it's an excellent place to start learning good ASP.NET application design. The most surprising revelation for experienced developers will probably be the simplicity of the project. For example, here are a few things you won't see in the IBuySpy examples:
- COM+ Serviced Components. The connection pooling features of ADO.NET mean that you may not need to use COM+ in a distributed application. ADO.NET automatically creates and retains a pool of database connections that can be handed out to any ASP.NET worker thread in need, avoiding some of the traditional overhead of a database call.
- Business Objects. Instead, IBuySpy uses an optimized design where pages talk to the database through a set of dedicated database classes. However, these classes (which we'll explore a little later) don't shoulder any responsibility for enforcing so-called "business rules," which generally need to be handled further upstream.
- Custom validation, authentication, or caching code. Workarounds were common for all three of these in typical ASP sites. In ASP.NET, platform services take care of all the heavy lifting.
So what are some of the key insights you can glean from playing with IBuySpy? Here are some examples:
- User controls organize UI functionality. For example, menu navigational controls are encapsulated in dedicated controls, which are then repeated on multiple pages. More complex inherited controls, which are primarily of interest to tool vendors, aren't used.
- Output caching is used to avoid the database. For example, the IBuySpy e-commerce site caches the output for the product category navigation control for 100 minutes. This means that changes to the product categories won't be picked up immediately. However, it also ensures that thousands of e-shoppers can shop at a time, without requiring more than one database trip. Data caching, which allows programmers to implement more flexible caching strategies, is avoided -- in fact, it isn't needed.
- Session state is avoided. Instead, per-user information (like the list of items in the shopping cart) is stored in the database. This gives the developer the freedom to retain shopping cart items for long periods of time, and not worry about wasting valuable server memory.
Perhaps the most interesting aspect is the strict tiered design of the IBuySpy case studies. The ASP.NET Web page code never talks directly to the database. Instead, the Web page code talks to a layer of database classes. A separate source code file is included for every table the application needs to use in the database. This file includes an entity class and a stateless service provider class.
For example, consider the Products table, which stores a list of items that can be purchased through the IBuySpy e-commerce storefront. It defines a class that represents an individual product row:
Public Class ProductDetails Public ModelNumber As String Public ModelName As String Public ProductImage As String Public UnitCost As Decimal Public Description As String End Class
It also defines a stateless class that encapsulates database operations for the table, like insertions, deletions, and queries. Here's the structure used for the Products table:
Public Class ProductsDB Public Function GetProducts(ByVal categoryID As Integer) _ As SqlDataReader ' (Code omitted.) End Function Public Function GetProductDetails(ByVal productID As Integer) _ As ProductDetails ' (Code omitted.) End Function Public Function GetProductsAlsoPurchased(ByVal productID As _ Integer) As SqlDataReader ' (Code omitted.) End Function Public Function GetMostPopularProductsOfWeek() As SqlDataReader ' (Code omitted.) End Function Public Function SearchProductDescriptions(ByVal searchString As _ String) As SqlDataReader ' (Code omitted.) End Function End Class
These classes interact with the database exclusively through stored procedures. This makes for fairly lengthy code, but it adds that invaluable extra layer of indirection, making it easier to modify the database without breaking your application code, and fine-tune security settings.
Here's an example that returns the most popular products of the week:
Public Function GetMostPopularProductsOfWeek() As SqlDataReader ' Create Instance of Connection and Command Object Dim myConnection As New SqlConnection( _ ConfigurationSettings.AppSettings("ConnectionString")) Dim myCommand As New SqlCommand("ProductsMostPopular", _ myConnection) ' Mark the Command as a SPROC myCommand.CommandType = CommandType.StoredProcedure ' Execute the command myConnection.Open() Dim result As SqlDataReader = _ myCommand.ExecuteReader(CommandBehavior.CloseConnection) ' Return the datareader result Return result End Function
Note that this code returns a
DataReader instead of a disconnected
DataSet. This ensures the best possible performance, because a
DataReader only retrieves a single record at a time. A
DataSet requires extra memory, because it holds the entire set of retrieved results in memory at once. In fact, you'll find that Microsoft very rarely uses the
DataSet in any of its case studies (aside from returning read-only information to Web service clients). The possible concurrency problems and the inefficiency of automatically generated commands means it's almost always easier to use direct database access.
As a side effect, this design also means the ASP.NET page code has to behave nicely, and close the
DataReader when finished. In most cases, this isn't a problem. For example, consider the code below, which displays the most popular items in a data-bound control when the page loads. The
DataBind() method automatically opens the connection, reads the results from the
DataReader, and closes it when complete.
Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Obtain list of favorite items Dim products As New IBuySpy.ProductsDB() ' Databind and display the list of favorite product items productList.DataSource = products.GetMostPopularProductsOfWeek() productList.DataBind() ' Hide the list if no items are in it If productList.Items.Count = 0 Then productList.Visible = False End If End Sub
To see all this and more in action, I encourage you to download the IBuySpy case studies and experiment with the code on your own. Who knows, it just might save you a few future headaches and battle scars.
Return to the .NET DevCenter.