http://www.developer.com/

Back to article

Creating a Weblog using JScript .NET and ASP.NET


February 8, 2002

The focus of this series to date is to get you familiar with JScript .NET and the .NET Framework from an overview perspective. The focus of this article is to guide you through a functioning ASP.NET application that uses XML. You can view and work with a live demonstration of the application here

Overview

A Web Log is a Web page that represents a personal journal - a web log is informally referred to as a "blog". Blogs have frequent updates and individual updates are usually short. Some web blogs, like Slashdot, have a number of people working on them and provide a degree of interactivity by allowing readers to post comments about blog entries.

Blogs have become so popular that services, like Blogger.com, make it easy to create and update your own web log. Developers like to do things their own way and have the ability to create their own blog - this article discusses how to create your own blog using JScript .NET and using ASP.NET

Some features of the sample Web log include:

  • Uses XML to store blog comments
  • Uses the DataSet and FileStream objects to interact with the XML database
  • Uses XSL to display the blog
  • Combines XSL and CSS for advanced formatting

The blog is made up of two pages: one that displays the contents of the blog (the application's default page), and one that allows you create a new blog entry. You can view and work with a live demonstration of the application here.

Designing the XML Database

Using an XML file to store the details of blog entries makes it easy to manage the data since you can directly edit the file using an application like notepad. If you're not familiar with XML, you can get a really quick overview in a great article called XML in 10 Points - the link is in the links section at the end of this article.

The XML database is made up of a single file that has a relatively flat structure. Here's a fragment of the XML database file:

<blogEntry>
  <dateYear>2002</dateYear>
  <dateMonth>1</dateMonth>
  <dateDay>21</dateDay>
  <dateHour>10</dateHour>
  <dateMinute>15</dateMinute>
  <author>Tom</author>
  <headline>Something to prove...</headline>
  <blogText>All I want is the chance to prove that
      money cannot make me happy. </blogText>
</blogEntry>

The file describes a blogEntry element that is made up of a number of other elements that describe the details of the blog entry. The date is made up of five elements to make it easier to sort the blog and to make it possible to allow another application or Web site to, perhaps, easily extract a single blog entry from the XML database. The remaining elements represent the blog entry itself.

If you're more comfortable working with a database, think of the blogEntry element as the name of a table, having fields represented by the blogEntry's child elements. In fact, I designed the XML database this way to make it easy to work with through an ADO.NET DataSet object, which is similar to an in-memory representation of a relational database.

Designing the New Blog Entry Page

Figure 1 is a partial screen shot of the ASP.NET page that allows users to add new entries to the blog.



Click here for larger image

Figure 1 - New Blog Entry Form

The page is referred to as a Web Form, since it incorporates some of ASP.NET server controls that make it easy to collect and validate the information the user enters. The page also uses Cascading Style Sheets (CSS) along with some inline formatting to achieve some of its visual effects.

Introducing ASP.NET Server Controls

An ASP.NET server control's declaration looks a lit like a regular HTML tag; however, when the control executes, it emits plain HTML that any browser can easily render. The benefit server controls offer over plain HTML controls (like an <input...> element) is that they offer programming model that abstracts the fact that some code executes in a user's browser and some code executes on the server.

For example, consider the text boxes in Figure 1. Each text box is a server control - the declaration for the large text box that represents the Entry field looks like this:

<asp:Textbox textmode="multiline" 
    id="blogEntry" columns="40" rows="8" runat="server" />

The declaration looks like an HTML tag apart from the fact that there's a runat attribute. When a user requests a page with the above control on it, ASP.NET renders the control in plain HTML as shown:

<textarea name="blogEntry" rows="8" cols="40"
             id="blogEntry"></textarea>

The runat attribute tells ASP.NET that you want the control to behave as if it executes on the server, but renders on the client. When a user enters text into the field, and submits it by clicking on the Save button, you can access the contents of the server control like this:

someVariable = blogEntry.Text;

If you wanted to access the same field in ASP, you'd have to write code that looks something like this:

someVariable = Request.Form("blogEntry");

The key difference between using an ASP.NET server control and an HTML control is that the ASP.NET server control appears, to the developer, to reside and execute on the server making developing Web based applications as easy as creating traditional applications that execute on the desktop.

ASP.NET server controls aren't limited to rendering their HTML counterparts. You can use special validator controls to validate the values a user enters. For example, the Name field is a required entrys for the form in Figure 1. The field is actually made up of two server controls: a textbox control and a RequiredFieldValidator control, as shown:

<asp:textbox text="" id="Name" runat="server" />

<asp:RequiredFieldValidator ControlToValidate="Name" 
    display="static" runat="server">*</asp:RequiredFieldValidator>

As the name suggests, the RequiredFieldValidator allows developers to easily specify that a user must provide a value for the control. The RequiredFieldValidator simply checks that there's something in the field - it does not confirm that the value is valid (you could use a RegularExpressionValidator control to validate the value a user enters - there's a link at the end of this article that provides the location of ASP.NET's validation controls). When ASP.NET renders the control, it generates some HTML and JScript client side code. Here's what the HTML looks like:

<input name="Name" type="text" id="Name" />
<span id="ctrl1" controltovalidate="Name" 
    evaluationfunction="RequiredFieldValidatorEvaluateIsValid"
    initialvalue="" style="color:Red;visibility:hidden;">*</span>

If the user does not enter a value into the field that the RequiredFieldValidator validates, then it displays an asterisk symbol next to the field when the user attempts to submit the form. The client side JScript code does not allow the user to submit the form until all of the required fields are filled in; in addition, the code executes on the client thereby reducing the processing load on the server.

When the all of the fields on a form are valid, they get submitted to the server, where processing takes place in the button's click event handler. Here's the declaration for the button control:

<asp:Button class="btn" id="submitBlog" Text="Save" 
    onClick="Submit_Click" runat="server" />

The Submit_Click function gets called when a user clicks on the button - you write the function inline with the rest of the ASP.NET page, and ASP.NET handles the details of when and how to call it. This is what the function's declaration looks like:

public function Submit_Click( sender:Object,  e:EventArgs)
{
    //...
}

This function is the workhorse of the application since it handles the details of interacting with the XML database file using the ADO.NET DataSet object, all of which I cover in the following section.

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[0].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[0].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[0].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.



Click here for larger image

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.

Summary

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:


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 essam@designs2solutions.com, or at his Web site

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date