Interested in .NET? Don’t miss SitePoint’s .NET Feature Guide — it’s an excellent resource!
Today, we’ll build a simple dynamic Website using ASP.NET, C#, XML and XSLT. I’ll assume you have prior working knowledge of ASP.NET, C#, XML and XSLT. The site source code provided here shows all details of the implementation, including error handling.
Why XML and XSLT?
There are a couple of different methods by which you can dynamically generate HTML, and they usually allow the writing of the presentation and the business logic to occur together. While writing small sites using these methods is fast, with the growth of the site, the lack of separation makes the maintenance and reuse of existing code difficult.
The XML/XSLT approach naturally separates the presentation from the underlying business logic. There are numerous benefits to this method:
- Better reuse of the business logic code in fast and reliable manner
- The creation of multiple alternative UI, so that serving clients with different capabilities or needs is easy and fast
- User customizations like co-branding, custom look and feel are possible
- Bigger development teams have specialized UI designers and core developers. The separation of the UI reduces the dependencies between the developers, thus improving productivity
- Even the UI unit testing is easier, as an XML document is all that’s needed to reproduce particular tests
But if the UI/business logic separation is the stem of all these benefits, why use XML and XSLT for it?
Think of XSLT as HTML, because most of the code in XSLT is HTML. Given, there couple of rules to observe and the HTML has to be well-formed, but it’s nothing dramatically different. There are free tools to help you convert HTML into XSLT. HTML Tidy, available at the W3C site, is one of them.
What about XML? Well, XML can represent any kind of data structure, and is used as input for the XSLT transformation. Most of the modern databases (SQL Server, Oracle, etc.) can return their query’s result in XML format. In addition, database APIs (ADO) also can convert their results into XML format.
The Site Architecture
The architecture of the site we’ll build is simple. For each HTTP request/response, the business logic and data management layers provide the appropriate data as XML. The presentation layer provides the XSLT and the different parameters for the transformation. The outcome of the XSLT transformation is HTML, which returns to the user.
The reusable presentation layer supports skins and globalization. The XSLT files are stored in a local directory called “UI”. Each “skin” set of XSLT files is stored in a sub directory that bears the skin name. Each globalization set of XSLT files is stored in a subdirectory with the locale id, under the skin directory. The default skin has no name, and the same goes for the globalization set.
Here is how the directory tree looks:
UI
skin1
lcid1
lcid2
skin2
The UI directories contain all the UI files required for the final rendering on the client side, including images, CSS, java script, and more.
The application-independent and reusable functionality is isolated in class UIEngine in the UIEngine.cs file.
The class exposes three properties: ApplicationBase,Skin and Lcid, as well as the methods Transform, ExtractUIParameters and ExtractAction. ApplicationBase contains the relative path of the UI directory, Skin contains the current skin for the request, and Lcid contains the locale of the request.
The Transform method converts the XML data using the specified style sheet, and writes the resulting HTML to the response object.
Transform(HttpContext ctx, XPathDocument xmldoc,
string stylesheet, int lcid, XsltArgumentList arg)
The Web Application Base
By default, the application base is extracted from Web.Config, which is the configuration file for the Web application. This is an XML file that contains the configuration of the Web application. The file contains numerous system settings for the site. There is a section for custom application settings called <appSettings>
. The configuration is accessible through ConfigurationSettings class, part of the System.Configuration namespace. The code is as simple as:
<configuration>
...
<appSettings>
<add key="ui.base" value="ui" />
</appSettings>
</configuration>
m_applicationBase = ConfigurationSettings.AppSettings["ui.base"];
The UI Engine determines the full path to the XSLT file using the application base, skin, locale and XSLT file name:
string path = m_applicationBase;
// check for skin
if(m_skin.Length > 0)
{
path += m_skin + "\";
}
// check for globalization
if(m_locale > 0)
{
path += m_locale.ToString() + "\";
}
// add visualization parameters
arg.AddParam("ui.path","",path);
arg.AddParam("ui.skin","",m_skin);
arg.AddParam("ui.locale","", m_locale.ToString());
// add the stylesheet
path += stylesheet;
To switch skins, we set the name of the skin in the query string:
http://localhost/bookstore/title.aspx?skin=dark
Since HTTP is stateless protocol, the skin name needs to be persisted across different calls. There are a couple of different methods: session variables, query parameters, cookies, and database persistence.
Session variables would be the preferred method if your site was designed to use sessions. Keeping the variables on the server reduces the complexity of the pages and the amount of data transferred back and forth. However, such sites don’t scale as well as sites that don’t use sessions, due to session management overhead.
The query parameters and cookies methods don’t require server sessions, and they scale better, but they also complicate the site design, and will be affected if the user turns off the browser support for cookies. Another issue is security: sensitive data such as credit card numbers shouldn’t be sent as part of query parameters or cookies, as the data won’t be encrypted if secure connections are not used.
Since the sample site doesn’t require sessions and there are few parameters, I choose to pass the state though the URL query’s parameters, that is, skin=dark is augmented automatically to each URL. This is done in the XSL files.
<a href="title.aspx?skin={$ui.skin}&lcid={$ui.locale}">...
A configuration page could be built that showed the available skins or locales. For example:
http://localhost/bookstore/title.aspx?skin=dark&lcid=1026
This URL will display the book list by title using skin “dark” and localized in Bulgarian — 1026. Note that only the UI is localized; the data itself is not. By default, the encoding of the HTML is UTF-8, which works with languages. However some browsers don’t support UTF-8, and locale-specific encoding might be added to the XSL stylesheets.
The visualization parameters are added as parameters to the XSLT transformation. They’re visible in the XSLT and can be used to find HTML elements as CSS and images. For example, we’d use the ui.path parameter to find the CSS file and the image:
<LINK REL="stylesheet" TYPE="text/css" HREF="{$ui.path}css/default.css" />
<img src="{$ui.path}view.gif" alt="view book details" border="0" />
Additional custom parameters for each XSLT file can be added via XsltArgumentList arg, part of the Transform method.
Once the relative path is determined, it is converted to the absolute path and loaded into XslTransform object:
XslTransform xslt = new XslTransform();
xslt.Load(ctx.Server.MapPath(path));
The XML Data is passed as string and loaded into and XPathDocument:
XPathDocument mydata = new XPathDocument(new
XmlTextReader(xmldoc,XmlNodeType.Document,null));
The transformation is performed and the result is written to a string:
StringWriter sw = new StringWriter();
xslt.Transform(mydata,arg,sw);
The string is written to the Response object with buffering turned on:
ctx.Response.BufferOutput = true;
ctx.Response.Write(sw.ToString());
ctx.Response.End();
Create the Web Pages
With the UIEngine done, next is the creation of the Web pages. There are at least two different methods we could use to accomplish this task. The defaut is to create Web Form aspx page and handle the rendering in the Load event. The new class is derived from System.Web.UI.Page and the Load event is handled by a method called Page_Load
. In the source code you’ll find an example of such a page, author.aspx, although it is disabled and not used.
The method that I use is to implement IHttpHandler
. Although I have to write it by hand (no wizard is currently available), the code is simple and it doesn’t require multiple source files. In addition, the compiled application is a single DLL: i.e. there are no page (aspx) files.
IHttpHandler
interface as part ofSystem.Web
namespace and lets you define custom handling of HTTP requests. The interface has one propertyIsReusable
, and one methodProcessRequest
. TheIsReusable
property is set to "true" to allow the object to be reused instead of being created every time is needed. To support session state, IRequiresSessionState has to be implemented as well.
How one would call this handler from a browser? You’ll need to map this handler to the incoming URLs. This is done in the Web.Config file:
<system.web>
...
<httpHandlers>
<add verb="*" path="*.aspx"
type="bookstore.UIHandler,bookstore" />
</httpHandlers>
</system.web>
The ‘verb’ attribute refers to the HTTP verb: GET POST, etc. Setting the value to ‘*
‘ allows any verb to be used. The ‘type’ attribute specifies the handler’s class name, complete with namespace, and the second part is the assembly name. Using the .Net wizard, set the assembly name to the name of project — “bookstore” in this case.
The ‘path’ attribute is more interesting. One can specified a specific URL such book.aspx, wild card name (*.aspx
) or even a path — books/*.aspx
. Using the Website name, the absolute URL in the three cases will look like this:
http://localhost/bookstore/book.aspx
http://localhost/bookstore/<anyname>.aspx
http://localhost/bookstore/books/<anyname>.aspx
Note that in the third case, the server path is considered to be bookstore/books and HttpContext.Server.MapPath would map the path accordingly.
The extension aspx is used only because it is already registered with the Web Server. A custom extension can be used, but the Web Server needs to know that ASP.NET will handle such extensions.
The API
The Website has a simple API. There are four resources (or virtual pages) or actions once the extensions are stripped:
author.aspx
— displays list of the books sorted by authortitle.aspx
— displays list of the books sorted by titlegenre.aspx
— displays list of the books sorted by genrebook.aspx
— display full details for the selected book
Each of these URLs takes the UI parameters ‘skin
‘ and ‘lcid
‘, which are optional. The book.aspx also requires ‘id’ parameter with the id of the book.
The request is handled in method ProcessRequest
. First, the UI parameters are extracted and the requested action is determined:
eng.ExtractUIParameters(ctx);
string action = eng.ExtractAction(ctx);
Then for each action, the XSLT is determined and the XML data is extracted from the business layer logic. In this sample all the actions use the same XML data, which is implemented in class CBookData
.
CBookData bd = new CBookData();
mydata = bd.GetData(ctx);
The action ‘book’ takes addition parameter id is extracted and added as parameter to the XSLT transformation. The other cases, such parameters, could be passed to the business logic layer as well.
string id = ctx.Request.QueryString["id"];
Finally the transformation is performed and the HTML page rendered in the user’s browser.
eng.Transform(ctx,mydata,stylesheet,arg);
Errors are handled by UIEngine.DisplayError
method, which displays all the caught exception’s messages in canned HTML.
catch(Exception err)
{
UIEngine.UIEngine.DisplayError(ctx,err.Message);
}
The error pages can be implemented using a XSLT stylesheets in the same fashion as the main UI.
Source Code and Installation Instructions
The source code is zipped in a file code.zip, which you can download here. There are two C# files UIEngine.cs and UIHandler.cs. The UI is under directory ‘UI’ and contains two skins — default and ‘dark’. The ‘dark’ skin is also localized for locale 1026. The data file ‘book.xml’ is in subdirectory data.
- Using VC.NET create new project using the wizard “Visual C# Projects| ASP.NET Web Application”. As the name of your project enter “bookstore”. You can choose a different name, but you’ll need to modify the namespace in UIHandler.cs and the httpHandlers in Web.Config to reflect that name.
<configuration>
...
<system.web>
...
<httpHandlers>
<add verb="*" path="*.aspx" type="bookstore.UIHandler,bookstore" />
</httpHandlers>
</system.web>
<appSettings>
<add key="ui.base" value="ui" />
</appSettings>
</configuration>
This simple Website harvests the ASP.NET functionality, the entire code is less then 300 lines long, without the UI. The code is readily reusable and the power of XML/XSLT will help you create and maintain dynamic Websites with little effort. For a real site, a production strength database needs to be added. That means designing database schema and database queries, which are very important in ensuring good performance and the scalabilty of the site. Then one would need a module to communicate with the database. ADO.NET is a good choice as an underlying database library. It can handle various databases, it is natively implemented in .NET and it can convert query results in XML format.
Good luck with your site!
This article is part of SitePoint’s .NET Feature Guide — an excellent resource for aspiring and experienced .NET developers. Don’t miss it!
Frequently Asked Questions (FAQs) about XML/XSLT Driven Website in .NET
What are the benefits of using XML/XSLT in .NET for website development?
XML/XSLT in .NET provides a robust and flexible framework for website development. It allows for the separation of data (XML) from presentation (XSLT), which makes it easier to manage and update the website. This separation also enhances the reusability of code, as the same XML data can be presented in different ways using different XSLT stylesheets. Moreover, XML/XSLT supports a wide range of data types and structures, making it suitable for complex web applications. Lastly, .NET provides built-in support for XML/XSLT, including powerful libraries and tools for parsing, transforming, and validating XML data.
How can I execute an XSLT transformation in .NET?
In .NET, you can use the XslCompiledTransform class to execute an XSLT transformation. This class provides methods for loading an XSLT stylesheet, transforming an XML document, and outputting the result. Here is a basic example:XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("stylesheet.xsl");
xslt.Transform("input.xml", "output.xml");
In this code, “stylesheet.xsl” is the XSLT stylesheet, “input.xml” is the XML document to be transformed, and “output.xml” is the output file.
How can I handle errors during XSLT transformation in .NET?
.NET provides the XsltException class for handling errors during XSLT transformation. This class contains information about the error, including the error message, the line number and position in the XSLT stylesheet where the error occurred, and the inner exception that caused the error. You can catch an XsltException in a try-catch block and handle it appropriately. For example:try
{
xslt.Transform("input.xml", "output.xml");
}
catch (XsltException ex)
{
Console.WriteLine("Error: " + ex.Message);
}
In this code, if an error occurs during the transformation, the catch block is executed and the error message is printed to the console.
Can I use XPath expressions in .NET?
Yes, .NET provides the XPathNavigator class for navigating an XML document using XPath expressions. This class provides methods for selecting nodes, evaluating expressions, and iterating over node sets. For example, you can select all “book” elements in an XML document like this:XPathDocument doc = new XPathDocument("books.xml");
XPathNavigator nav = doc.CreateNavigator();
XPathNodeIterator iter = nav.Select("/catalog/book");
while (iter.MoveNext())
{
Console.WriteLine(iter.Current.Value);
}
In this code, “/catalog/book” is the XPath expression, and iter.Current.Value is the value of the current “book” element.
How can I validate an XML document against an XML Schema in .NET?
.NET provides the XmlReaderSettings and XmlReader classes for validating an XML document against an XML Schema. You can set the Schemas property of an XmlReaderSettings object to an XmlSchemaSet containing your schema, and then create an XmlReader with these settings. The XmlReader will validate the XML document as it reads it. For example:XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(null, "schema.xsd");
settings.ValidationType = ValidationType.Schema;
XmlReader reader = XmlReader.Create("document.xml", settings);
while (reader.Read()) { }
In this code, “schema.xsd” is the XML Schema, and “document.xml” is the XML document to be validated. If the document is not valid, an XmlSchemaValidationException is thrown.
How can I generate XML data from a database in .NET?
.NET provides the DataSet and DataTable classes for working with data in a database. You can fill a DataSet or DataTable with data from a database, and then write this data to an XML file using the WriteXml method. For example:DataSet ds = new DataSet();
// Fill the DataSet with data from the database...
ds.WriteXml("data.xml");
In this code, “data.xml” is the output XML file. The WriteXml method writes the data in the DataSet to this file in XML format.
How can I parse an XML document in .NET?
.NET provides the XmlDocument and XmlReader classes for parsing an XML document. The XmlDocument class provides a DOM-like interface for working with XML data, while the XmlReader class provides a forward-only, read-only cursor for reading XML data. For example, you can load an XML document into an XmlDocument like this:XmlDocument doc = new XmlDocument();
doc.Load("document.xml");
In this code, “document.xml” is the XML document to be parsed. After loading the document, you can navigate and manipulate the XML data using the methods and properties of the XmlDocument and XmlNode classes.
How can I create an XSLT stylesheet in .NET?
Creating an XSLT stylesheet in .NET is similar to creating an XML document. You can use the XmlWriter class to write the XSLT elements and attributes to a file. For example:XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create("stylesheet.xsl", settings))
{
writer.WriteStartDocument();
writer.WriteStartElement("xsl", "stylesheet", "http://www.w3.org/1999/XSL/Transform");
// Write the XSLT elements and attributes...
writer.WriteEndElement();
writer.WriteEndDocument();
}
In this code, “stylesheet.xsl” is the output XSLT stylesheet. The WriteStartElement and WriteEndElement methods are used to write the start and end tags of the XSLT elements, and the WriteAttributeString method is used to write the attributes.
How can I use namespaces in XML/XSLT in .NET?
.NET provides the XmlNamespaceManager class for handling namespaces in XML/XSLT. This class allows you to add namespace declarations to a collection, and then use these declarations when selecting nodes with an XPathNavigator or validating an XML document with an XmlReader. For example:XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("bk", "http://www.example.com/books");
XPathNavigator nav = doc.CreateNavigator();
XPathNodeIterator iter = nav.Select("/bk:catalog/bk:book", nsmgr);
In this code, “bk” is the prefix associated with the namespace “http://www.example.com/books“. The Select method of the XPathNavigator uses this prefix to select the “book” elements in the “catalog” element.
How can I transform an XML document with parameters in .NET?
.NET allows you to pass parameters to an XSLT transformation using the XsltArgumentList class. You can add parameters to an XsltArgumentList with the AddParam method, and then pass this argument list to the Transform method of the XslCompiledTransform class. For example:XsltArgumentList args = new XsltArgumentList();
args.AddParam("param1", "", "value1");
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load("stylesheet.xsl");
xslt.Transform("input.xml", args, "output.xml");
In this code, “param1” is the name of the parameter, “” is the namespace URI of the parameter (empty in this case), and “value1” is the value of the parameter. The Transform method uses these parameters during the transformation.
Victor is the principle engineer for Keynote Systems, Inc, which provides Web performance management and testing services to enable customers to benchmark, diagnose, assure and improve the end-to-end performance of their ebusiness applications and systems. Victor has spent the last 10 years in Silicon Valley. He has a masters in nuclear engineering, has worked as software developer for more than 12 years and has developed multiple desktop and server applications.