Page and Data Caching in .Net

15. January 2003 15:17 by Chris in dev  //  Tags:   //   Comments (0)

Note that this article was first published on 02/01/2003. The original article is available on DotNetJohn, where the code is also available for download and execution.

 

Introduction

In this article we’re going to take a look at the features available to the ASP.NET programmer that enable performance improvement via the use of caching. Caching is the keeping of frequently used data in memory for ready access by your ASP.NET application. As such caching is a resource trade-off between those needed to obtain the data and those needed to store the data. You should be aware that there is this trade-off - there is little point caching data that is going to be requested infrequently as this is simply wasteful of memory and may have a negative impact on your system performance. However, on the other hand, if there is data that is required every time a user visits the home page of your application and this data is only going to change once a day, then there are big resource savings to be made by storing this data in memory rather than retrieving this data every time a user hits that homepage. This even considering that it is likely that the DBMS will also be doing it’s own caching. Typically you will want to try and minimise requests to your data store as, again typically, these will be the most resource hungry operations associated with your application.

In ASP.NET there are two areas where caching techniques arise:

  • Caching of rendered pages, page fragments or WebService output: termed ‘output caching’. Output caching can be implemented either declaratively or programmatically.
  • Caching of data / data objects programmatically via the cache class.

We'll return to the Cache class later in the article, but let’s focus on Output Caching to start with and Page Output Caching in particular.

You can either declaratively use the Output Caching support available to web forms/pages, page fragments and WebServices as part of their implementation or you can cache programmatically using the HttpCachePolicy class exposed through the HttpResponse.Cache property available within the .NET Framework. I'll not look at WebServices options in any detail here, only mentioning that the WebMethod attribute that is assigned to methods to enable them as Webservices has a CacheDuration attribute which the programmer may specify.

Page Output Caching

Let’s consider a base example and then examine in a little detail the additional parameters available to us programmers. To minimally enable caching for a web forms page (or user controls) you can use either of the following:

1. Declarative specification via the @OutputCache directive e.g.:

 <%@ OutputCache Duration="120" VaryByParam="none" %>  
 

 

2. Programmatic specification via the Cache property of the HttpResponse class, e.g.:

 Response.Cache.SetExpires(datetime,now,addminutes(2))
 Response.Cache.SetCacheability(HttpCacheability.Public) 

 

These are equivalent and will cache the page for 2 minutes. What does this mean exactly? When the document is initially requested the page is cached. Until the specified expiration all page requests for that page will be served from the cache. On cache expiration the page is removed from the cache. On the next request the page will be recompiled, and again cached.

In fact @OutputCache is a higher-level wrapper around the HttpCachePolicy class exposed via the HttpResponse class so rather than just being equivalent they ultimately resolve to exactly the same IL code.

Looking at the declarative example and explaining the VaryByParam="none". HTTP supports two methods of maintaining state between pages: POST and GET. Get requests are characterised by the use of the query string to pass parameters, e.g. default.aspx?id=1&name=chris, whereas post indicates that the parameters were passed in the body of the HTTP request. In the example above caching for such examples based on parameters is disabled. To enable, you would set VaryByParam to be ‘name’, for example – or any parameters on which basis you wish to cache. This would cause the creation of different cache entries for different parameter values. For example, the output of default.aspx?id=2&name=maria would also be cached. Note that the VaryByParam attribute is mandatory.

Returning to the programmatic example and considering when you would choose this second method over the first. Firstly, as it’s programmatic, you would use this option when the cache settings needed to be set dynamically. Secondly, you have more flexibility in option setting with HttpCachePolicy as exposed by the HttpResponse.cache property.

You may be wondering exactly what

 Response.Cache.SetCacheability(HttpCacheability.Public) 

achieves. This sets the cache control HTTP header - here to public - to specify that the response is cacheable by clients and shared (proxy) caches - basically everybody can cache it. The other options are nocache, private and server.

We’ll return to Response.Cache after looking at the directive option in more detail.

The @OutputCache Directive

First an example based on what we've seen thus far: output caching based on querystring parameters:

Note this example requires connectivity to a standard SQLServer installation, in particular the Northwind sample database. You maye need to change the string constant strConn to an appropriate connection string for your system for the sample code presented in this article to work. If you have no easy access to SQLServer, you could load some data in from an XML file or simply pre-populate a datalist (for example) and bind the datagrid to this datastructure.

output_caching_directive_example.aspx

 <%@ OutputCache Duration="30" VaryByParam="number" %>
 <%@ Import Namespace="System.Data" %>
 <%@ Import Namespace="System.Data.SqlClient" %>
 
 <html>
 <head></head>
 <body>
 
 <a href="output_caching_directive_example.aspx?number=1">1</a>-
 <a href="output_caching_directive_example.aspx?number=2">2</a>-
 <a href="output_caching_directive_example.aspx?number=3">3</a>-
 <a href="output_caching_directive_example.aspx?number=4">4</a>-
 <a href="output_caching_directive_example.aspx?number=5">5</a>-
 <a href="output_caching_directive_example.aspx?number=6">6</a>-
 <a href="output_caching_directive_example.aspx?number=7">7</a>-
 <a href="output_caching_directive_example.aspx?number=8">8</a>-
 <a href="output_caching_directive_example.aspx?number=9">9</a>
 
 <p>
 <asp:Label id="lblTimestamp" runat="server" maintainstate="false" />
 <p>
 <asp:DataGrid id="dgProducts" runat="server" maintainstate="false" />
 
 </body>
 </html>
 

 <script language="vb" runat="server">
 
 const strConn = "server=localhost;uid=sa;pwd=;database=Northwind"
 
 Sub Page_Load(sender as Object, e As EventArgs)
 
   If Not Request.QueryString("number") = Nothing Then
     lblTimestamp.Text = DateTime.Now.TimeOfDay.ToString()
 
     dim SqlConn as new SqlConnection(strConn)
     dim SqlCmd as new SqlCommand("SELECT TOP " _
       & Request.QueryString("number") & _
       " * FROM Products", SqlConn)
     SqlConn.Open()
 
     dgProducts.DataSource = SqlCmd.ExecuteReader(CommandBehavior.CloseConnection)
     Page.DataBind()
 
   End If
 End Sub
 
 </script> 

Thus, if you click through some of the links to the parameterised pages and then return to them you will see the timestamp remains the same for each parameter setting until the 30 seconds has elapsed when the data is loaded again. Further caching is performed per parameter file, as indicated by the different timestamps.

The full specification of the OutputCache directive is:

 <%@ OutputCache Duration="#ofseconds" 
                   Location="Any | Client | Downstream | Server | None" 
                   VaryByControl="controlname" 
                   VaryByCustom="browser | customstring" 
                   VaryByHeader="headers" 
                   VaryByParam="parametername" %> 

Examining these attributes in turn:

Duration
This is the time, in seconds, that the page or user control is cached. Setting this attribute on a page or user control establishes an expiration policy for HTTP responses from the object and will automatically cache the page or user control output. Note that this attribute is required. If you do not include it, a parser error occurs.

Location
This allows control of from where the client receives the cached document and should be one of the OutputCacheLocation enumeration values. The default is Any. This attribute is not supported for @OutputCache directives included in user controls. The enumeration values are:
Any: the output cache can be located on the browser client (where the request originated), on a proxy server (or any other server) participating in the request, or on the server where the request was processed.
Client: the output cache is located on the browser client where the request originated.
Downstream: the output cache can be stored in any HTTP 1.1 cache-capable devices other than the origin server. This includes proxy servers and the client that made the request.
None: the output cache is disabled for the requested page.
Server: the output cache is located on the Web server where the request was processed.

VaryByControl
A semicolon-separated list of strings used to vary the output cache. These strings represent fully qualified names of properties on a user control. When this attribute is used for a user control, the user control output is varied to the cache for each specified user control property. Note that this attribute is required in a user control @OutputCache directive unless you have included a VaryByParam attribute. This attribute is not supported for @OutputCache directives in ASP.NET pages.

VaryByCustom
Any text that represents custom output caching requirements. If this attribute is given a value of browser, the cache is varied by browser name and major version information. If a custom string is entered, you must override the HttpApplication.GetVaryByCustomString method in your application's Global.asax file. For example, if you wanted to vary caching by platform you would set the custom string to be ‘Platform’ and override GetVaryByCustomString to return the platform used by the requester via HttpContext.request.Browser.Platform.

VaryByHeader
A semicolon-separated list of HTTP headers used to vary the output cache. When this attribute is set to multiple headers, the output cache contains a different version of the requested document for each specified header. Example headers you might use are: Accept-Charset, Accept-Language and User-Agent but I suggest you consider the full list of header options and consider which might be suitable options for your particular application. Note that setting the VaryByHeader attribute enables caching items in all HTTP/1.1 caches, not just the ASP.NET cache. This attribute is not supported for @OutputCache directives in user controls.

VaryByParam
As already introduced this is a semicolon-separated list of strings used to vary the output cache. By default, these strings correspond to a query string value sent with GET method attributes, or a parameter sent using the POST method. When this attribute is set to multiple parameters, the output cache contains a different version of the requested document for each specified parameter. Possible values include none, *, and any valid query string or POST parameter name. Note that this attribute is required when you output cache ASP.NET pages. It is required for user controls as well unless you have included a VaryByControl attribute in the control's @OutputCache directive. A parser error occurs if you fail to include it. If you do not want to specify a parameter to vary cached content, set the value to none. If you want to vary the output cache by all parameter values, set the attribute to *.

Returning now to the programmatic alternative for Page Output Caching:

Response.Cache

As stated earlier @OutputCache is a higher-level wrapper around the HttpCachePolicy class exposed via the HttpResponse class. Thus all the functionality of the last section is also available via HttpResponse.Cache. For example, our previous code example can be translated as follows to deliver the same functionality:

 

output_caching_programmatic_example.aspx

 <%@ Import Namespace="System.Data" %>
 <%@ Import Namespace="System.Data.SqlClient" %>
 
 <html>
 <head></head>
 <body>
 
 <a href="output_caching_programmatic_example.aspx?number=1">1</a>-
 <a href="output_caching_programmatic_example.aspx?number=2">2</a>-
 <a href="output_caching_programmatic_example.aspx?number=3">3</a>-
 <a href="output_caching_programmatic_example.aspx?number=4">4</a>-
 <a href="output_caching_programmatic_example.aspx?number=5">5</a>-
 <a href="output_caching_programmatic_example.aspx?number=6">6</a>-
 <a href="output_caching_programmatic_example.aspx?number=7">7</a>-
 <a href="output_caching_programmatic_example.aspx?number=8">8</a>-
 <a href="output_caching_programmatic_example.aspx?number=9">9</a>
 
 <p>
 <asp:Label id="lblTimestamp" runat="server" maintainstate="false" />
 
 <p>
 
 <asp:DataGrid id="dgProducts" runat="server" maintainstate="true" />
 
 </body>
 </html>
 

 <script language="vb" runat="server">
 
 const strConn = "server=localhost;uid=sa;pwd=;database=Northwind"
 
 Sub Page_Load(sender as Object, e As EventArgs)
 
   Response.Cache.SetExpires(dateTime.Now.AddSeconds(30))
   Response.Cache.SetCacheability(HttpCacheability.Public)
   Response.Cache.VaryByParams("number")=true
 
   If Not Request.QueryString("number") = Nothing Then
 
     lblTimestamp.Text = DateTime.Now.TimeOfDay.ToString()
 
     dim SqlConn as new SqlConnection(strConn)
     dim SqlCmd as new SqlCommand("SELECT TOP " _
       & Request.QueryString("number") & " * FROM Products", SqlConn)
     SqlConn.Open()
 
     dgProducts.DataSource = SqlCmd.ExecuteReader(CommandBehavior.CloseConnection)
     Page.DataBind()
 
   End If
 End Sub
 
 </script> 

The three lines of importance are:

Response.Cache.SetExpires(dateTime.Now.AddSeconds(30))
Response.Cache.SetCacheability(HttpCacheability.Public)
Response.Cache.VaryByParams("number")=true

It is only the third line you’ve not seen before. This is equivalent to VaryByParam="number" in the directive example. Thus you can see that the various options of the OutputCache directive are equivalent to different classes exposed by Response.Cache. Apart from the method of access the pertinent information is, unsurprisingly, very similar to that presented above for the directive version.

Thus, in addition to VaryByParams there is a VaryByHeaders class as well as a SetVaryByCustom method. If you are interested in the extra functionality exposed via these and associated classes I would suggest you peruse the relevant sections of the .NET SDK documentation.

Fragment Caching

Fragment caching is really a minor variation of page caching and almost all of what we’ve described already is relevant. The ‘fragment’ referred to is actually one or more user controls included on a parent web form. Each user control can have different cache durations. You simply specify the @OutputCache for the user controls and they will be cached as per those specifications. Note that any caching in the parent web form overrides any specified in the included user controls. So, for example, if the page is set to 30 secs and the user control to 10 the user control cache will not be refreshed for 30 secs.

It should be noted that of the standard options only the VaryByParam attribute is valid for controlling caching of controls. An additional attribute is available within user controls: VaryByControl, as introduced above, allowing multiple representations of a user control dependent on one or more of its exposed properties. So, extending our example above, if we implemented a control that exposed the SQL query used to generate the datareader which is bound to the datagrid we could cache on the basis of the property which is the SQL string. Thus we can create powerful controls with effective caching of the data presented.

Programmatic Caching: using the Cache Class to Cache Data

ASP.NET output caching is a great way to increase performance in your web applications. However, it does not give you control over caching data or objects that can be shared, e.g. sharing a dataset from page to page. The cache class, part of the system.web.caching namespace, enables you to implement application-wide caching of objects rather than page wide as with the HttpCachePolicy class. Note that the lifetime of the cache is equivalent to the lifetime of the application. If the IIS web application is restarted current cache settings will be lost.

The public properties and methods of the cache class are:

Public Properties

Count: gets the number of items stored in the cache.

Item: gets or sets the cache item at the specified key.

Public Methods

Add: adds the specified item to the Cache object with dependencies, expiration and priority policies, and a delegate you can use to notify your application when the inserted item is removed from the Cache.

Equals: determines whether two object instances are equal.

Get: retrieves the specified item from the Cache object.

GetEnumerator: retrieves a dictionary enumerator used to iterate through the key settings and their values contained in the cache.

GetHashCode: serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures like a hash table.

GetType: gets the type of the current instance.

Insert: inserts an item into the Cache object. Use one of the versions of this method to overwrite an existing Cache item with the same key parameter.

Remove: removes the specified item from the application's Cache object.

ToString: returns a String that represents the current Object.

We'll now examine some of the above to varying levels of detail, starting with the most complex, the insert method:

Insert

Data is inserted into the cache with the Insert method of the cache object. Cache.Insert has 4 overloaded methods with the following signatures:

Overloads Public Sub Insert(String, Object)

Inserts an item into the Cache object with a cache key to reference its location and using default values provided by the CacheItemPriority enumeration.

Overloads Public Sub Insert(String, Object, CacheDependency)

Inserts an object into the Cache that has file or key dependencies.

Overloads Public Sub Insert(String, Object, CacheDependency, DateTime, TimeSpan)

Inserts an object into the Cache with dependencies and expiration policies.

Overloads Public Sub Insert(String, Object, CacheDependency, DateTime, TimeSpan, CacheItemPriority, CacheItemRemovedCallback)

Inserts an object into the Cache object with dependencies, expiration and priority policies, and a delegate you can use to notify your application when the inserted item is removed from the Cache.

Summary of parameters:

String the name reference to the object to be cached
Object the object to be cached
CacheDependency file or cache key dependencies for the new item
Datetime indicates absolute expiration
Timespan sliding expiration – object removed if greater than timespan after last access
CacheItemPriorities an enumeration that will decide order of item removal under heavy load
CacheItemPriorityDecay an enumeration; items with a fast decay value are removed if not used frequently
CacheItemRemovedCallback a delegate that is called when an item is removed from the cache

Picking out one of these options for further mention: CacheDependency. This attribute allows the validity of the cache to be dependent on a file or another cache item. If the target of such a dependency changes, this can be detected. Consider the following scenario: an application reads data from an XML file that is periodically updated. The application processes the data in the file and represents this via an aspx page. Further, the application caches that data and inserts a dependency on the file from which the data was read. The key aspect is that when the file is updated .NET recognizes the fact as it is monitoring this file. The programmer can interrogate the CacheDependency object to check for any updates and handle the situation accordingly in code.

Remove

Other methods of the cache class expose a few less parameters than Insert. Cache.Remove expects a single parameter – the string reference value to the Cache object you want to remove.

 Cache.Remove(“MyCacheItem”) 

Get

You can either use the get method to obtain an item from the cache or use the item property. Further, as the item property is the default property, you do not have to explicitly request it. Thus the latter three lines below are equivalent:

 Cache.Insert(“MyCacheItem”, Object)
 Dim obj as object
 obj = Cache.get(“MyCacheItem”)
 obj = Cache.Item("MyCacheItem")
 obj = Cache(“MyCacheItem”) 

GetEnumerator

Returns a dictionary (key/ value pairs) enumerator enabling you enumerate through the collection, adding and removing items as you do so if so inclined. You would use as follows:

 dim myEnumerator as IDictionaryEnumerator
 myEnumerator=Cache.GetEnumerator()
 
 While (myEnumerator.MoveNext)
   Response.Write(myEnumerator.Key.ToString() & “<br>”)
   'do other manipulation here if so desired
 End While 

An Example

To finish off with an example, we’ll cache a subset of the data from our earlier examples using a cache object.

cache_class_example.aspx

 <%@ Import Namespace="System.Data" %>
 <%@ Import Namespace="System.Data.SqlClient" %>
 
 <html>
 <head></head>
 <body>
 <asp:datagrid id="dgProducts" runat="server" maintainstate="false" />
 </body>
 </html>
 

 <script language="vb" runat="server">
 
 public sub Page_Load(sender as Object, e as EventArgs)
 
   const strConn = "server=localhost;uid=sa;pwd=;database=Northwind"
 
   dim dsProductsCached as object = Cache.Get("dsProductsCached")
 
   if dsProductsCached is nothing then
 
     Response.Write("Retrieved from database:")
     dim dsProducts as new DataSet()
     dim SqlConn as new SqlConnection(strConn)
     dim sdaProducts as new SqlDataAdapter("select Top 10 * from products", SqlConn)
     sdaProducts.Fill(dsProducts, "Products")
     dgProducts.DataSource = dsProducts.Tables("Products").DefaultView
 
     Cache.Insert("dsProductsCached", dsProducts, nothing, _
       DateTime.Now.AddMinutes(1), TimeSpan.Zero)
 
   else
 
     Response.Write("Cached:")
 
     dgProducts.DataSource = dsProductsCached
 
   end if
 
   DataBind()
 
 end sub </script> 

The important concept here is that if you view the above page, then within 1 minute save and view the same page after renaming it, you will receive the cached version of the data. Thus the cache data is shared between pages/ visitors to your web site.

Wrapping matters up

A final few pointers for using caching, largely reinforcing concepts introduced earlier, with the latter two applying to the use of the cache class:

  • Don't cache everything: caching uses memory resources - could these be better utilized elsewhere? You need to trade-off whether to regenerate items, or store them in memory.
  • Prioritise items in the cache: if memory is becoming a limited system resource .NET may need to release items from the cache to free up memory. Each time you insert something into the cache, you can use the overloaded version of Insert that allows you to indicate how important it is that the item is cached to your application. This is achieved using one of the CacheItemPriority enumeration values.
  • Configure centrally. To maximize code clarity and ease of maintenance store your cache settings, and possibly also instantiate your cache objects, in a key location, for example within global.asax.

I hope this article has served as a reasonably complete consideration of the caching capabilities available within ASP.NET and you are now aware, if you were not before, of the considerable possible performance savings available reasonably simply via the provided functionality. If you have any comments on the article, particularly if you believe there are any errors that should be corrected let me know at chris.sully@cymru-web.net.

References

ASP.NET: Tips, Tutorial and Code
Scott Mitchell et al.
Sams

Professional ASP.NET
Sussman et al.
Wrox

.NET SDK documentation

Various online articles

You may run output_caching_directive_example.aspx here.
You may run output_caching_programmatic_example.aspx here.
You may run cache_class_example.aspx here.
You may download the code here.

Understanding How to Use XSL Transforms

2. January 2003 15:11 by Chris in dev  //  Tags:   //   Comments (0)

Note that this article was first published on 02/01/2003. The original article is available on DotNetJohn, where the code is also available for download and execution.

Original abstract: XSLT, XPATH and how to apply the concepts in .NET. Examines the concept of transformation and how an XSLT stylesheet defines a transformation by describing the relationship between an input tree and an output tree. Continues to look at the structure of a stylesheet, its main sub-components and introduces examples of what you might expect to see therein. Finally, the article examines how to utilise XSLT stylesheets in .NET.

Knowledge assumed: reasonable understanding of XML and ASP.NET / VB.NET.

Introduction

XML represents a widely accepted mechanism for representing data in a platform-neutral manner. XSLT is the XML based language that has been designed to allow transformation of XML into other structures and formats, such as HTML or other XML documents. XSLT is a template-based language that works in collaboration with the XPath language to transform XML documents.

Note that not all applications are suited to such an approach though there are benefits to be derived in all but the simplest problem domains. Suitable applications for implementation with XML/ XSLT are

  • those that require different views of the same data – hence delivering economies of scale to the developer/ organisation.
  • those where maintaining the distinction between data and User Interface elements (UI) is an important consideration – for example for facilitating productivity through specialisation within a development team.

 

.NET provides an XSLT processor which can take as input XML and XSLT documents and, via matching nodes with specified output templates, produce an output document with the desired structure and content.

I’ll examine the processor and the supporting classes as far as XSLT within .NET is concerned in the latter half of this article. First, XSLT:

XSLT

I’m only going to be able to scratch the surface of the XSLT language here but I shall attempt to highlight a few of the key concepts. It is important to remember that XSLT is a language in its own right and, further, it is one in transition only having been around for a few years now. It’s also a little different in mechanism to most you may have previously come across. XSLT is basically a declarative pattern matching language, and as such requires a different mindset and a little getting used to. It’s (very!) vaguely like SQL or Prolog in this regard. Saying that, there are ways to ‘hook in’ more conventional procedural code.

If its not too late, now would be a good time to get round to stating what the acronym XSLT stands for: eXtensible Stylesheet Language: Transformations. XSLT grew from a bigger language called XSL – as it developed the decision was made to split XSL into areas corresponding to XSLT for defining the structural transformations, and ‘the rest’ which is the formatting process of rendering the output. This may commonly be as pixels on a screen, for example, but could also be several other alternatives. ‘The rest’ is still officially called XSL, though has also come to be known as XSL-FO (XSL Formatting objects). That’s the last time we’ll mention XSL-FO.

As XSLT developed it became apparent that there was overlap between the expression syntax in XSLT for selecting parts of a document (XPath), and the XPointer language being developed for linking one document to another. The sensible decision was made to define a single language to undertake both purposes. XPath acts as a sub-language within an XSLT stylesheet. An XPath expression may be used for a variety of functions but typically it is employed to identify parts of the input XML document for subsequent processing. I’ll make no significant further effort in the following discourse to emphasise the somewhat academic distinction between XPath and XSLT, the former being such an important, and integral, component of the latter.

A typical XSLT stylesheet consists of a sequence of template rules, defining how elements should be processed when encountered in the XML input file. In keeping with the declarative nature of the XSLT language, you specify what outputs should be produced by particular input patterns, as distinct from a procedural model where you define the sequence of tasks to be performed.

A tree model similar to the XML DOM is employed by both XSLT and XPath. The different types existing in an XML document can be represented by different types of node in a tree view. In XPath the root node is not an element, the root is the parent of the outermost element, representing the document as a whole. The XSLT tree model can represent every well-formed XML document, as well as documents that are not well formed according to the W3C.

An XPath tree is made up 7 types of node largely corresponding to elements in the XML source document: root, element, text, attribute, comment, processing instruction and namespace. Each node has metadata created from the source document, in accordance with the type of node under consideration. Considering the node type in a little more detail:

As already mentioned the root node is a singular node that should not be confused with the document element – an outermost element that contains all elements in a valid XML document.

Element and attribute refer to your XML entities, e.g.

 <product id=’1type=’book>XSLT for Beginners</product> 

product is an element and id and type are attributes.

Comments nodes represent comments in the XML source written between <!-- and -->. Similarly processing instructions are represented in thw XML source between <? and ?> tags. Note, however, that the XML commonly found as the first element of the XML document is only impersonating a processing instruction – it is not represented as a node in the tree.

A text node is a sequence of characters in the PCDATA (‘parsed character’ data) part of an element.

The XML source tree is converted to a result tree via transformation by the XSLT processor using the instructions of the XSL stylesheet. Time for an example or two:

Most stylesheets contain a number of template rules of the form:

 <xsl:template match="/">
   <xsl:message>Started!</xsl:message>
   <html>
     . . . do other stuff . . .
   </html>
 </xsl:template> 

where the . . . do other stuff . . . might contain further template bodies to undertake further processing, e.g.

 <xsl:template match="/">
   <xsl:message>Started!</xsl:message>
   <html>
     <head></head>
     <body>
       <xsl:apply-templates>
     </body>
   </html>
 </xsl:template> 

As previously stated, both the input document and output document are represented by a tree structure. So, the <body> element above is a literal element that is simply copied over from the stylesheet to the result tree.

<xsl:apply-templates/> means select all the children of the current node in the source tree, finding the matching template rule for each one in the stylesheet and apply it. The results depend on the content of both the stylesheet and the XML document under consideration. Actually, if there is no template for the root node, the built in template is invoked which processes all the children of the root node.

Thus, the simplest way to process an XML document is to write a template rule for each kind of node that might be encountered, or at least that we are interested in and want to process. This is an example of ‘push’ processing and can be considered to be similar logically to Cascading StyleSheets (CSS) where one document defines the structure (HTML/ XML), and the second (the stylesheet) defines the appearance within this structure. The output is conditional on the structure of the XML document.

Push processing works well when the output is to have the same structure and sequence of data as the input, and the input data is predictable.

Listing 1: simple XML file: books.xml

 <?xml version="1.0"?>
 <Library>
   <Book>
     <Title>XSLT Programmers Reference</Title>
     <Publisher>Wrox</Publisher>
     <Edition>2</Edition>
     <Authors>
       <Author>Kay, Michael</Author>
     </Authors>
     <PublishedDate>April 2001</PublishedDate>
     <ISBN>1-816005-06-7</ISBN>
   </Book>
   <Book>
     <Title>Dynamical systems and fractals</Title>
     <Publisher>Cambridge University Press</Publisher>
     <Authors>
       <Author>Becker, Karl-Heinz</Author>
       <Author>Dorfler, Michael</Author>
       <Author>David Sussman</Author>
     </Authors>
     <PublishedDate>1989</PublishedDate>
     <ISBN>0-521-36910-X</ISBN>
   </Book>
 </Library> 

Listing 2: Example of push processing of books.xml: example1.xslt

 <?xml version="1.0"?>
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
 <xsl:template match="Library">
   <html>
   <head></head>
   <body>
   <h1>Library</h1>
   <table border="1">
   <tr>
   <td><b>Title</b></td>
   <td><b>PublishedDate</b></td>
   <td><b>Publisher</b></td>
   </tr>
   <xsl:apply-templates/>
   </table>
   </body>
   </html>
 </xsl:template>
 
 <xsl:template match="Book">
   <tr>
     <xsl:apply-templates select="Title"/>
     <xsl:apply-templates select="PublishedDate"/>
     <xsl:apply-templates select="Publisher"/>
   </tr>
 </xsl:template>
 
 <xsl:template match="Title | PublishedDate | Publisher ">
   <td><xsl:value-of select="."/></td>
 </xsl:template>
 
 </xsl:stylesheet>

Note that in the book template I’ve used <xsl:apply-templates select=" … rather than just <xsl:apply-templates … This is because there is data in the source XML in which we are not interested and if we just let the built in template rules do their stuff the additional data would be copied across to the output tree. I’ve already mentioned the existence of built in template rules: when apply-templates is invoked to process a node and there is no matching template rule in the stylesheet a built in template rule is used, according to the type of the node. For example, for elements apply-templates is called on child nodes and for text and element nodes the text is copied over to the result tree. Try making the modification and viewing the results.

Using the select attribute of apply-templates is one solution - being more careful about which nodes to process (rather than just saying ‘process all children of the current node). Another is to be more precise about how to process them (rather than just saying ‘choose the best-fit template rule). This is termed ‘pull’ processing and is achieved using the value-of command:

<xsl:value-of select=”price” />

In this alternative pull model the stylesheet provides the structure and the document acts wholly as a data source. Thus a ‘pull’ version of the above example would be:

Listing 3: Example of push processing of books.xml: example2.xslt

 <?xml version="1.0"?>
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
 <xsl:template match="/">
   <xsl:apply-templates/>
 </xsl:template>
 
 <xsl:template match="Library">
   <html>
   <head></head>
   <body>
   <h1>Library</h1>
   <table border="1">
   <tr>
   <td><b>Title</b></td>
   <td><b>PublishedDate</b></td>
   </tr>
     <xsl:apply-templates/>
   </table>
   </body>
   </html>
 </xsl:template>
 
 <xsl:template match="Book">
   <tr>
   <td><xsl:value-of select="Title"/></td>
   <td><xsl:value-of select="PublishedDate"/></td>
   </tr>
 </xsl:template>
 
 </xsl:stylesheet> 

These two examples are not hugely different but it is quite important you understand the small but important differences for future situations when you encounter more complex source documents and stylesheets. You can rely on the structure of the XML source document using template matching (push) or explicitly select elements, pulling them into the output document.

Other commands/ points worthy of note at this juncture (there are hundreds more for you to explore) are:

<xsl: for-each> which as you might guess, performs explicit processing of each of the specified nodes in turn.

<xsl: call-templates> invokes a specific template by name, rather than relying on pattern matching.

<xsl: apply-templates> can also take a mode attribute which allows you to make multiple passes through the XML data representation.

I’ve briefly introduced the basic XSLT concepts and, in particular, the push and pull models. The pull model is characterised by a few large templates and use of the <xsl:value-of> element so that the stylesheet controls the order of items in the output. In comparison the push model tends more towards smaller templates with the output largely following the structure of the XML source document.

I mentioned earlier that XSLT is often thought of as a declarative language. However, it also contains the flow control and looping instructions consistent with a procedural language. Typically, a push model stylesheet emphasizes the declarative aspects of the language, while the pull model emphasizes the procedural aspects.

Note the use of the word ‘typical’ - most stylesheets will contain elements of both push and pull models. However, it is useful to keep the two models in mind as it can make your stylesheet development simpler.

There you have it – we’ve scratched the surface of the XSLT and XPath languages and I’ll leave you to explore further. Both Wrox and O’Reilly have several books on the subject that have been well reviewed … take your pick if you want to delve deeper. Let me know if you’d like me to write another article on XSLT, building on what I’ve introduced here.

Time to see what .NET has to offer.

XSLT in .NET

First point of note: you can perform XSLT processing on the server or client (assuming your client browser has an XSLT processor). The usual client vs. server arguments pervade here: chiefly you’d like to utilise the processing power of the client machine rather than tying up server resources but can you be sure the client browser population is fit for purpose? If the answer to the latter is yes – the main requirement being that the XSLT you’ve written doesn’t generate errors in the client browser processor – then you can simply reference the XSLT stylesheet from the XML file and the specified transformation will be undertaken.

Returning to the server side processing options: you won’t be surprised to learn that it is the system.xml namespace where the classes and other namespaces relating to XSLT are found. The main ones are:

1. XpathDocument (system.xml.xpath)
This provides the faster option for XSLT transformation as it provides read only, cursor style access to XML data via its DOM. It has no public properties or methods to remember but does have several different constructors by which it accepts the following objects: XmlTextReader, textReader, stream and string path to an XML document.

2. XslTransform (system.xml.xsl)
This is the XSLT processor and hence the key class of interest to us. Three main steps to utilise: instantiate the transform object, loads the XSLT document into it and then transform the required XML document (accessed via the XPathDocument object created for the purpose).

3. XsltArgumentList:
Allows provision of parameters to XslTransform. XSLT defines a xsl:param element that can be used to hold information passed into the stylesheet from the XSLT processor. XslTArgumentList is used to achieve this.

Also of direct relevance are: XmlDocument and XmlDataDocument but I won’t be considering them further here … I’ll leave this to your own investigation.

Going to go straight to a simple example showing 1 and 2 and above in action:

Listing 4: .NET example: Transform.aspx

 <%@ Page language="vb" trace="false" debug="false"%>
 <%@ Import Namespace="System.Xml" %>
 <%@ Import Namespace="System.Xml.Xsl" %>
 <%@ Import Namespace="System.Xml.XPath" %>
 <%@ Import Namespace="System.IO" %>
 
 <script language="VB" runat="server">
   public sub Page_Load(sender as Object, e as EventArgs)
     Dim xmlPath as string = Server.MapPath("books.xml")
     Dim xslPath as string = Server.MapPath("example2.xslt")
 
     Dim fs as FileStream = new FileStream(xmlPath,FileMode.Open, FileAccess.Read)
     Dim reader as StreamReader = new StreamReader(fs,Encoding.UTF8)
     Dim xmlReader as XmlTextReader = new XmlTextReader(reader)
 
     'Instantiate the XPathDocument Class
     Dim doc as XPathDocument = new XPathDocument(xmlReader)
 
     'Instantiate the XslTransform Class
     Dim xslDoc as XslTransform = new XslTransform()
     xslDoc.Load(xslPath)
     xslDoc.Transform(doc,nothing,Response.Output)
 
     'Close Readers
     reader.Close()
     xmlReader.Close()
   end sub
 </script> 

As you can see this example uses the stylesheet example2.xsl as introduced earlier. Describing the code briefly: on page load strings are defined as the paths to the input files in the local directory. A FileStream object is instantiated and the XML document loaded into it. From this a StreamReader object is instantiated, and in turn a XmlTextReader from this. The DOM can then be constructed within the XPathDocument object from the XML source via the objects so far defined. We then need to instantiate the XSLTransform object, load the stylesheet as defined by the string xslPath, and actually call the transform method. The parameters are the XPathDocument object complete with DOM constructed from the XML document, any parameters passed to the stylesheet – none in this case, and the output destination of the result tree.

ASP.NET also comes complete with the ASP:Xml web control, making it easy to perform simple XSLT transformations in your ASP.NET pages. Use is as per any other web control, you simply supply the 2 input properties (DocumentSource and TransformSource) as parameters, either declaratively or programmatically. Here’s an example that does both, for demonstration and clarification purposes:

Listing 5: ASP:xml web control: Transform2.aspx

 <%@ Page language="vb" trace="true" debug="true"%>
 
 <script language="vb" runat="server">
 sub page_load()
   xslTrans.DocumentSource="books.xml"
   xslTrans.TransformSource="example2.xslt"
 end sub
 </script>
 
 <html>
 <body>
 <asp:xml id="xslTrans" runat="server" 
             DocumentSource="books.xml" TransformSource="example2.xslt" />
 
 </body>
 </html> 

Lastly, just to leave you with the thought that the place of XML/XSLT technology in the ASP.NET model is not clear-cut, as the server controls generate their own HTML. Does this leave XSLT redundant? Well, no … but we may need to be a little more creative in our thinking. For example, the flexibility of XML/XSLT can be combined with the power of ASP.NET server controls by using XSLT to generate the server controls dynamically, thus leveraging the best of both worlds. Perhaps I’ll leave this for another article. Let me know if you are interested.

References:

ASP.NET: Tips, tutorials and code Sams
XSLT Programmers Reference 2nd Edition Wrox
Professional XSL Wrox
Various Online Sources

You may run Transform.aspx by clicking Here.
You may run Transform2.aspx by clicking Here.
You may download the code by clicking Here.

About the author

I am Dr Christopher Sully (MCPD, MCSD) and I am a Cardiff, UK based IT Consultant/ Developer and have been involved in the industry since 1996 though I started programming considerably earlier than that. During the intervening period I've worked mainly on web application projects utilising Microsoft products and technologies: principally ASP.NET and SQL Server and working on all phases of the project lifecycle. If you might like to utilise some of the aforementioned experience I would strongly recommend that you contact me. I am also trying to improve my Welsh so am likely to blog about this as well as IT matters.

Month List