IE 8 Web Slices - part 1

Recently I've been looking at IE 8 and the new features that have been added, web slices and accelerators. They're both useful, web slices in particular since it can be used in any kind of information you want to keep updated like a sales order for example. It also helps that Microsoft has released the features as an open specification, Firefox has an add-on called WebChunks instead of Web Slices.

In order to really try it out and see if web slices could be useful, I created a small ASP.NET project that has an order page and uses an RSS feed for the web slice, implemented via an ASP.NET Httphandler. The sample project can be downloaded here.

I've split up this into two blog posts because it would be such a lengthy post otherwise. This post is part 1 and explains the order page, part 2 will describe the Httphandler and the RSS feed.

This is an example of what this web slice looks like in IE 8.












This sample was created using C# on Vista / IIS 7, VS 2008 and SQL Server 2008. The database is using the AdventureWorks sample database. If you haven't installed it, you can download it from here.

I have not used any particular features that require IIS 7 or SQL Server 2008. I strongly recommend that you download and use one of the public Virtual PC 2007 images Microsoft have released that has IE 8 installed instead of installing the IE 8 beta on your main machine.

If you extract the zip file, you will see the 4 projects the solution contains:

  • SalesOrderWeb - main ASP.NET which include the sample order page
  • SalesOrderRSS - Httphandler and RSS feed logic
  • DAL - our data layer which retrieves the order information from the database
  • BusinessEntity - the domain layer, just contains the properties
The ASP.NET page only contains one page, Order.aspx. The page will either retrieve a random order from the database or retrieve a specific order if the user is requesting to view the order from the web slice. In our sample, the user will navigate to the same order page but in a real world scenario the user would probably navigate to a different view where they can track the order, contact customer service and so forth.

The Page_Load method contains the bulk of the work on this page.


protected void Page_Load(object sender, EventArgs e)
{

if (!Page.IsPostBack)
{
string salesOrderId = string.Empty;
DAL.SalesOrder dal = new SalesOrder();

// If this is an existing order
if (Request.QueryString.Count > 0 && Request.QueryString["SalesOrderId"] != null)
{
salesOrderId = Request.QueryString["SalesOrderId"].ToString();
}
else
{
salesOrderId = dal.RetrieveRandomSalesOrderId();
}

SalesHeader sh = dal.RetrieveSalesOrder(salesOrderId);
_salesHeader = sh;

GridView1.DataSource = sh.SalesDetails;
GridView1.DataBind();

labelSalesOrderNumber.Text = salesOrderId;

// Embed in the page the url of the RSS feed for this sales order.
SetRSSFeedURL(salesOrderId);
}
}

If we don't have a "SalesOrderId" value in the query string, we will assume this is a new order and we call into our data layer to retrieve a random order from the database. The bulk of the work to retrieve the random order in the DAL can be seen below.


using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandText = "SELECT TOP 1 SalesOrderID FROM Sales.SalesOrderHeader ORDER BY NEWID()";

conn.Open();

using (SqlDataReader dr = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
while (dr.Read())
{
salesOrderId = dr.GetInt32(0).ToString();
}
}
}
The use of the NEWID() SQL Server method is an interesting way to generate a random value like in our case but normally I would generate a random number in the code and not add extra work to the database.

After we have a SalesOrderId regardless if we retrieved a random sales order or via an existing order, we retrieve the details from the DAL via the RetrieveSalesOrder method. It contains quite a few inner joins, normally I would create a stored procedure or view but for ease of setting up this sample I decided to just set the sql in a StringBuilder object.


StringBuilder sqlStatement = new StringBuilder();
sqlStatement.Append("SELECT sh.SalesOrderNumber");
sqlStatement.Append(",sh.[Status]");
sqlStatement.Append(",sh.ShipDate");
sqlStatement.Append(",sh.DueDate");
sqlStatement.Append(",sh.TotalDue");
sqlStatement.Append(",sh.TaxAmt");
sqlStatement.Append(",sh.Freight");
sqlStatement.Append(",sd.LineTotal");
sqlStatement.Append(",sd.OrderQty");
sqlStatement.Append(",p.Name");
sqlStatement.Append(" FROM Sales.SalesOrderHeader sh");
sqlStatement.Append(" INNER JOIN Sales.SalesOrderDetail sd");
sqlStatement.Append(" On sh.SalesOrderId = sd.SalesOrderId");
sqlStatement.Append(" INNER JOIN Production.Product p");
sqlStatement.Append(" On sd.ProductID = p.ProductID");
sqlStatement.AppendFormat(" WHERE sh.SalesOrderId = {0}", salesOrderId);

After we have retrieved the sales order details, we set the results to the gridview. The DAL returns the domain business classes to the UI. Returning a SqlDataReader or similar one of the worst ways of coupling the database implementation to the presentation. I personally dislike returning a dataset too because it's such a bloated xml mess.

Finally, we call the SetRSSFEEDUrl method to set the RSS feed url for the web slice for this particular order. Because we are using a master page, we need to do a little extra work to find the "feedurl" control where we set the url.


private void SetRSSFeedURL(string salesOrderNumber)
{
// Find the feedurl href so we can set the correct url (RSS feed).
// Since this is on a master page, we have to through some hoops.
ContentPlaceHolder contentPlaceHolder;

contentPlaceHolder = (ContentPlaceHolder)Master.FindControl("ContentPlaceHolder1");
if (contentPlaceHolder != null)
{
Control ctl = (Control)contentPlaceHolder.FindControl("feedurl");

if (ctl != null)
{
HtmlAnchor anchor = (HtmlAnchor)ctl;

if (anchor != null)
{
anchor.HRef = string.Format("http://{0}/SalesOrderWeb/{1}/OrderTracking.rss"
, System.Net.Dns.GetHostName()
, salesOrderNumber
);
}
}
}
}
There are couple of things of interest that we have to set up in the web.config file. We are using a custom configuration section to store the default values for our RSS feed like title, description and so forth. First we define the type at the top of the web.config file like the below configuration shows.



At the bottom of the web.config, we display the default values like this.



We also set up the connection string to the database using the regular connection string element that was introduced with ASP.NET 2.0



Lastly, we set up the httphandler. We specify that we only want this handler to fire for GET requests. We also define that it should only run if the URL for this site ends with "OrderTracking.rss". Any other ".rss" handlers will be ignored unless they are listed in the web.config or machine.config. We also set the type where the httphandler can be loaded.



If we have set everything up correctly and publish the solution to our web site, our order page should look something like this when we "create" a new order.



That's it!