Creating a Weblog using JScript .NET and ASP.NET, Page 2
Adding a new blog entry
As I mentioned earlier, I designed the XML database to make it easy to work with through the ADO.NET DataSet object since it is easier to use than some of the other .NET Class Library classes and provides reasonable performance in this application. The code (in the Submit_Click function) that reads the XML database file from the server's disk looks like this:
var dataFile:String = "db/d2sWebLog.xml" ; var blogDataSet:DataSet = new DataSet(); blogDataSet.ReadXml(Server.MapPath(dataFile)); var newBlogEntry:DataRow = blogDataSet.Tables.NewRow();
The second line in the listing creates the DataSet object, and the third line populates the DataSet by reading from the physical file on the system's disk (by first mapping the relative location of the file to its physical location using the Server.MapPath method). The last thing the code does is add a new row to the DataSet object in preparation for filling it in with the values the user entered onto the form in Figure 1.
The DataSet object is very good at inferring the schema, the logical structure, of an XML file; however, there is a performance penalty. When the NewRow method gets called in the last line of the previous listing, the DataSet object must determine what fields to add to the new row and what their types are. If you don't provide a schema, the DataSet object creates one; however, in this scenario, the structure of the XML database does not change between visits. As a result, it does not make sense to have the DataSet infer the structure of the database based on its content at runtime. The XML database includes schema information to make it easier for the DataSet object to determine the layout of the file. Here's what the XML Schema looks like:
<xsd:schema id="WebLog" targetNamespace="" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:element name="WebLog" msdata:IsDataSet="true"> <xsd:complexType> <xsd:choice maxOccurs="unbounded"> <xsd:element name="blogEntry"> <xsd:complexType> <xsd:sequence> <xsd:element name="dateYear" type="xsd:string" minOccurs="0" /> <xsd:element name="dateMonth" type="xsd:string" minOccurs="0" /> <xsd:element name="dateDay" type="xsd:string" minOccurs="0" /> <xsd:element name="dateHour" type="xsd:string" minOccurs="0" /> <xsd:element name="dateMinute" type="xsd:string" minOccurs="0" /> <xsd:element name="author" type="xsd:string" minOccurs="0" /> <xsd:element name="headline" type="xsd:string" minOccurs="0" /> <xsd:element name="dateTime" type="xsd:string" minOccurs="0" /> <xsd:element name="blogText" type="xsd:string" minOccurs="0" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:complexType> </xsd:element> </xsd:schema>
If you're not familiar with XML Schemas, the schema essentially describes each field in terms of its name, data type, and any restrictions it has. For example, the schema describes the dateYear element as a string type (xsd:string) that's optional (minOccurs=0). The links at the end of this article include the address of where to get more information about XML Schemas and working with them through a DataSet object.
The values the user provided on the form get transferred to the new row as shown once the new row is in place. The code fills in the date* fields based on the system's current time, using the DateTime structure, as shown:
var newBlogEntry:DataRow = blogDataSet.Tables.NewRow(); newBlogEntry["dateYear"] = DateTime.Now.Year; newBlogEntry["dateMonth"] = DateTime.Now.Month; newBlogEntry["dateDay"] = DateTime.Now.Day; newBlogEntry["dateHour"] = DateTime.Now.Hour; newBlogEntry["dateMinute"] = DateTime.Now.Minute; newBlogEntry["author"] = Name.Text; newBlogEntry["headline"] = Subject.Text; newBlogEntry["blogText"] = blogEntry.Text;
Once the new row is ready, the code first appends it to the end of the DataSet and then writes it out to the XML database file, as shown:
blogDataSet.Tables.Rows.Add(newBlogEntry); blogDataSet.WriteXml( Server.MapPath(dataFile), XmlWriteMode.WriteSchema);
The WriteXml method takes the physical location of the XML database file and an argument that tells the function to include the schema in the XML file when it writes it out (to avoid having to infer the schema again when the application reads the database through the DataSet object).
The last action the code in the Submit_Click function takes is to redirect the user to the default.aspx page, which displays the contents of the blog.
Displaying the blog
Figure 2 shows what the blog looks like when a user first accesses it. The page is delivered to users in HTML combined with some CSS, which most browsers can accurately render.
Figure 2 - Displaying the blog
The page that's responsible for generating the output is default.aspx, which is made up of only five lines of JScript .NET code. The page accomplishes the feat of displaying the blog by taking advantage of XSL - Extensible Stylesheet Language. XSL is an XML vocabulary that's consumed by an XSL processor to transform XML into other formats, including HTML. The links at the end of this article include a pointer to a tutorial on transforming XML into HTML using XSLT.
The XSL file is called blog.xsl and is located in the db directory along with the d2sWebLog.xml file (the XML database file). The XSL file essentially builds a table that also includes formatting based on a CSS file that's used throughout the application's pages. The following listing is a fragment from the beginning of the XSL file:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" /> <xsl:template match="/"> <html> <head> <title>ASP.NET Web Log using XML</title> <LINK href="blogstyle.css" type="text/css" rel="stylesheet" /> </head> <body> <table width="70%" border="0" cellspacing="0" cellpadding="0" align="center"> <xsl:apply-templates /> </table> </body> </html> </xsl:template>
I removed the parts of the XSL that aren't relevant to the discussion - the actual beginning of XSL file is much longer. The listing indicates that the output method is HTML, as specified in the xsl:outputelement. The XSL basically says, for every root element in the XML document, render the template that appears between the <xsl:template...>and </xsl:template> tags. If you look closely at the HTML, you'll notice that it links in the blogstyle.css cascading style sheet file and then renders the HTML body which contains a declaration for a table. The table tag includes an xsl:apply-templates element (shown in bold in the listing) - the XSL processor interprets this element as a request to review the rest of the XSL to find other xsl:template elements which will fill in the remaining table.
There's another xsl:template that causes the XSL processor to review the XML database file, searching for WebLog elements (WebLog is the database's root element). When the XSL processor finds a WebLog element, it processes it by reviewing the instructions that follow the xsl:template element. The listing that follows shows the XSL that causes the details of a blog entry to render within tr and td tags, which reside within the table from the previous listing.
<xsl:template match="//WebLog"> <xsl:for-each select="blogEntry"> <xsl:sort select="dateYear" order="descending"/> <xsl:sort select="dateMonth" order="descending"/> <xsl:sort select="dateDay" order="descending"/> <xsl:sort select="dateHour" order="descending"/> <xsl:sort select="dateMinute" order="descending"/> <tr> <td class="tblhd" bgcolor="6699cc"> <xsl:value-of select="headline"/> </td> </tr> <tr class="tblbody"> <td bgcolor="b6cbeb"> <small>Posted by <xsl:value-of select="author" /> on <xsl:value-of select= "dateYear"/>-<xsl:value-of select="dateMonth"/> -<xsl:value-of select="dateDay"/> @ <xsl:value-of select= "dateHour"/>:<xsl:value-of select="dateMinute"/> </small> </td> </tr> <tr> <td class="blogText"> <xsl:value-of select="blogText" /><br/><br/> </td> </tr> <tr> <td style="border-top: 1 solid #808080;color=#ffffff"></td> </tr> </xsl:for-each> </xsl:template>
The XSL also includes xsl:sorte lements that cause the blog to display in reverse chronological order (newest entries first); as a result, you don't have to be concerned with what order the entries are made in the XML database file.
The code in the ASP.NET page that initiates processing follows:
var doc:XmlDocument= new XmlDocument(); var xslDoc : XslTransform = new XslTransform(); doc.Load(Server.MapPath("db/d2sWebLog.xml")); xslDoc.Load(Server.MapPath("db/blog.xsl")); xslDoc.Transform(doc,null,Response.Output);
It's straightforward, if you're familiar with the .NET Class Library. The code loads the XML database into an instance of an XmlDocument object and the XSL file into an instance of an XslTransform object. The code initiates the XSL processor, to transform the XML into HTML based on the XSL, in the last line of the listing. The Transform method's last parameter is the Output property of the Response object; as a result, the output is sent directly to the client without any further processing.
Overview of the distribution
The sample code that's included with this article contains all of the ASP.NET, XML, and XSL code. You can run the code on a Windows 2000 or Windows XP system that has the .NET Framework installed on it. Alternately, you can try a live sample here [http://www.designs2solutions.com/redirect.asp?item=xmlBlog].
To install, simply unzip the file to a folder (make sure that the "Use directory names" feature has a check beside it) and set up that directory as a virtual directory for your Web server. The easiest way to do that is to start Windows Explorer, locate the directory, and right-click on its name. Select the Properties option from the pop-up menu, select the Web Sharing tab, click on the "Share this folder" radio button, and click on "Ok" on the resulting dialog box. If you want to be able to write to the blog using the form in Figure 1, you'll need to change the permissions on the db folder to grant the IUSR_* account write permission so that it can modify the file.
This article covered a lot of ground: ASP.NET server controls, XML, XSL, CSS, and using several classes from the .NET Class Library. The next article in this series looks at Web Services: why they're interesting, how to create them, and how to consume them from an ASP.NET, Windows Forms, and Internet Explorer-based client.
Where to get more information
Here are some resources that may help you understand some of the concepts this article introduced and discussed:
- ASP.NET: Web Forms Let You Drag and Drop Your Way to Powerful Web Apps
- Working with XML through a DataSet object
- Validation Server Controls Reference
- XML in 10 Points
- Using W3C XML Schema
- HTML and XSLT
- Download the sample code [5K]
Essam Ahmed is the author of "JScript .NET Programming" (ISBN 0764548689, Published by Hungry Minds September 2001), many articles (including some at CodeGuru.com) and book reviews (also available at CodeGuru.com). Contact Essam at email@example.com, or at his Web site