Click here to Skip to main content
15,880,405 members
Articles / Web Development / CSS

An XML Based (we)blog with RSS Feed

Rate me:
Please Sign up or sign in to vote.
4.59/5 (18 votes)
7 Jul 20038 min read 402.6K   2.3K   111   38
A BLOG tool ready to use. Post weblogs to an XML file from a windows application via Web Service. Uses SOAP Headers for authentication. Uses simple XSL Transformation for the RSS feed.

Sample Image - weblog.gif

Introduction

Since writing weblogs, or blogging as it is also called, has become pretty popular the last year, I thought of constructing my own blog tool. Blog is a shorter word for a web log, an online (most often public) journal where the author writes down his or her thoughts, sometimes around a specific topic. This article describes how to write a pretty simple weblog application and a windows program for writing entries sitting in the system tray.

Some of the techniques used in this application are XML and XML Schema, Web Services, DataSets, Cache and the Calendar Web Control. Oh, and the XML Control too for transforming the XML weblog into RSS.

The Web Application

The web application consists of three parts actually; the web page showing the log and a calendar, a password protected web service for accepting entries and finally a second web page, which transforms the internal XML file into a RSS 2.0 feed via XSL transformation.

The Windows Application

The windows application (from now on called the client) is fairly simple in functionality and consists of a single dialog where the user can type in a message and send it over to the web site via a Web Service call.

The client sits in the system tray all the time, and when the user wants to write a message in his or her weblog, a click with the mouse brings up the dialog, ready for use.

Using the Code

Let’s go over some of the more interesting parts of the code, starting with the XML format for the weblog data.

The Weblog XML and Schema

XML
<?xml version="1.0" standalone="yes"?>
<weblog>
  <logentry>
    <id>0a8d4ec3-eec1-4b07-b26f-98bb5561f43c</id>
   
<logtitle>A
  title</logtitle>
    <logtime>2003-01-10T13:28:14.2031250+01:00</logtime>
    <logtimeGMT>Fri, 10 Jan 2003 13:28:14 GMT</logtimeGMT>
    <logtext>This is an entry in the weblog.</logtext>
  </logentry>
</weblog>

And the XML Schema for the weblog:

XML
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="weblog" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="weblog" msdata:IsDataSet="true" msdata:Locale="sv-SE">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element name="logentry">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="id" type="xs:string" minOccurs="0" />
              <xs:element name="logtitle" type="xs:string" minOccurs="0" />

              <xs:element name="logtime" type="xs:date" minOccurs="0" />
              <xs:element name="logtimeGMT" type="xs:string" minOccurs="0" />
              <xs:element name="logtext" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

As the XML and the schema shows, the weblog consists of a number of log entries containing data for id, logtitle, logtext, logtime and logtimeGMT. The logtimeGMT is for the RSS feed, since it needs to be in RFC 822 format. I couldn’t find any simple way of transforming logtime into GMT with XSLT so I took the lazy path and stored both of them in the XML file. The id tag is a unique id that is given to each new blog entry.

The weblog Web Page

The weblog is presented on the web page by reading the XML file into a DataSet and binding that to a Repeater. I like the Repeater for simple loops like this, why use the more complex DataGrid or DataList when it’s not needed?

Remember to turn off the ViewState of the Repeater, it’s not needed and will speed up the loading of the page.

Every call to the page starts by getting the cached DataSet from the XML file. This is done in the Page_Load event.

VB.NET
Private Sub Page_Load(ByVal sender As System.Object, _
                      ByVal e As System.EventArgs) _
 Handles MyBase.Load

    'always start with getting our cached dataset
    dsWebLog = XmlHelper.GetDS()

    If Not IsPostBack Then
        SetDates()
        BindList()
    End If
End Sub

The XmlHelper class has a few static methods for reading and writing the XML DataSet.

The location of the XML file is stored in the ASP.NET configuration file, web.config.

VB.NET
Public Shared Function GetDS() As DataSet
    'get DS from cache
    Dim ds As DataSet = CType(HttpContext.Current.Cache("dsWebLog"), DataSet)
    If ds Is Nothing Then
        ds = New DataSet("weblog")
        ds.ReadXmlSchema(ConfigurationSettings.AppSettings("xmlSchema"))
        Try
            ds.ReadXml(ConfigurationSettings.AppSettings("xmlFile"))
        Catch ex As Exception
            'missing an xml file is perfectly fine, this might be the
            'first time the app is used
        End Try
        'store in cache with dependency to the xml file
        HttpContext.Current.Cache.Insert("dsWebLog", ds, _
    New Caching.CacheDependency(ConfigurationSettings.AppSettings("xmlFile")))
    End If
    Return ds
End Function

The cache has a dependency to the XML file, so the .NET Cache will automatically flush the cached DataSet if a new message is added to the XML file.

To be able to select a certain date, I also added the ASP.NET Calendar control to the page. When the page is loaded, I loop through all the dates in the weblog XML DataSet and select all the dates in the calendar that has an entry in the weblog. When someone clicks a certain date in the calendar, the DataSet is filtered before it’s bound to the Repeater.

VB.NET
Private Sub Calendar1_SelectionChanged(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles Calendar1.SelectionChanged
     dateFilter = Calendar1.SelectedDate.AddDays(1).ToString
     SetDates()
     BindList()
End Sub

Before the DataSet is bound to the Repeater, the log entries are sorted and only the top 50 entries are shown. This (as so much else in the sample app) can be set in the web.config file.

VB.NET
Private Sub BindList()
    'get a dataview from a copy of the cached dataset
    Dim dvWeblog As DataView = dsWebLog.Copy.Tables(0).DefaultView

    'filter on date?
    If dateFilter <> "" Then
        dvWeblog.RowFilter = "logtime < '" & dateFilter & "'"
    End If
    'sort it by date and time
    dvWeblog.Sort = "logtime desc"
    'copy maximum nr of rows to show
    Dim dtWeblog As DataTable = XmlHelper.GetTopRows(dvWeblog,
            ConfigurationSettings.AppSettings("maxrows"))

    'bind the sorted and stripped log to the repeater
    weblogList.DataSource = dtWeblog
    weblogList.DataBind()
End Sub

The DataSet is filtered by setting the RowFilter property of the DataView. The .NET Cache has a pointer to our cached DataSet, and the cached DataSet has a pointer to the DataView, so if we don’t take a copy of the DataSet, the RowFilter property will be the same for other users of the cached DataSet. Something I discovered the hard way...

VB.NET
'get a dataview from a copy of the cached dataset
 Dim dvWeblog As DataView = dsWebLog.Copy.Tables(0).DefaultView

The method called GetTopRows is also located in the XmlHelper class, and it copies a specific number of rows from the log to be displayed in the page.

VB.NET
Public Shared Function GetTopRows(ByVal dv As DataView, _
                                  ByVal Rows As Integer) As DataTable
    Dim dtReturn As DataTable
    Dim cRow As Integer
    Dim maxRows As Integer

    maxRows = dv.Count
    dtReturn = dv.Table.Clone()

    For cRow = 0 To (Rows - 1)
        If cRow = maxRows Then Exit For

        dtReturn.ImportRow(dv(cRow).Row)
    Next

    Return dtReturn
End Function

The weblog Client

The client is made up from a single dialog, which starts up minimized to the system tray, i.e., as an icon in the status area of the desktop. The dialog has a TextBox for the title, RichTextBox for the body text and a couple of buttons for sending the log entry to the Web Service and for hiding or closing the program.

So, to post some text to the weblog Web Service, the user types some text in the title textbox and in the body textbox, then presses the Send-button. I thought the Web Service should have some way of protection, so therefore the call is authenticated with a password sent in the SOAP Header. The password is stored in a config file, and I use the built in .NET ConfigurationSettings file (WeblogClient.exe.config) for this.

Update: To be able to type in formatted text with different colors and fonts, and also to be able to type in HTML or XML tags, the text in the RichTextBox is first converted to HTML (RichTextBoxUtil.ConvertToHTML()). You can have a look at the utility class called RichTextBoxUtil.vb to see how it is done. Note that the utility doesn't handle links yet.

VB.NET
Private Sub Send_Click(ByVal sender As System.Object, _
                      ByVal e As System.EventArgs) Handles SendButton.Click
        'Send message to the Weblog via SOAP/Webservice
        Dim wsWeblog As New Weblog.Weblog()
        'get password from the config file (WeblogClient.exe.config)
        Dim password As String = ConfigurationSettings.AppSettings("password")
        If password Is Nothing Then
            ConfigError("password")
        End If
        'this is our SOAP Header class
        Dim authentication As New Weblog.AuthHeader()
        authentication.password = password
        wsWeblog.AuthHeaderValue = authentication
        'get the Web Service URL from the config file
        Dim URL As String = ConfigurationSettings.AppSettings("wsPostURL")
        If URL Is Nothing Then
            ConfigError("URL")
        End If
        'set the correct URL for the Web Service
        wsWeblog.Url = URL
        'send HTML converted text to the weblog webservice        
        wsWeblog.PostMessage(TextBox1.Text, _
                             RichTextBoxUtil.ConvertToHTML(RichTextBox1))
        WindowState = FormWindowState.Minimized
        HideMe()

        'clear out the textbox
        Me.RichTextBox1.Clear()
         Me.TextBox1.Clear()
    End Sub

The URL for the Web Service is also stored in the config file (WebLogClient.exe.config), which must be located in the same directory as the weblog client.

The Web Service

The Web Service method for receiving and storing the posted message is quite small. It's one simple method, and it first checks the SOAP Header and compares the password, then it stores the posted message to the weblog.

VB.NET
<WebMethod(Description:="Post a message to the weblog. An authentication 
SOAP header is mandatory."), SoapHeader("authentication")> _ 
Public Function
    PostMessage(ByVal title As String, ByVal message As String) As Integer
    If authentication.password = ConfigurationSettings.AppSettings("password")_
    Then
        'password
        is ok, stor message in the XML file XmlHelper.AddMessage(title,
        message)
    Else
        Throw New Exception("Invalid password")
    End If
End Function

The password is (as so much else) stored in the web.config file.

The AddMessage() method just adds a new DataRow in the weblog DataSet and saves it back to XML. The method also creates a unique id for this posting. The new DataRow is added at the top of the DataSet. The XML file is stored at the location specified by the web.config file (default is at c:\weblog.xml).

VB.NET
Public Shared Sub AddMessage(ByVal title As String, _
                             ByVal message As String)
    Dim dsWebLog As DataSet = XmlHelper.GetDS
    Dim drNew As DataRow
    drNew = dsWebLog.Tables(0).NewRow
    drNew.Item("id") = Guid.NewGuid.ToString
    drNew.Item("logtitle") = title
    drNew.Item("logtime") = Now
    drNew.Item("logtimeGMT") = Format(Now, "r") 'RFC 822 format
    drNew.Item("logtext") = message
    dsWebLog.Tables(0).Rows.InsertAt(drNew, 0) 'insert it at beginning

    'store xml again
    dsWebLog.WriteXml(ConfigurationSettings.AppSettings("xmlFile"))
End Sub

The weblog RSS 2.0 Feed

More and more of the weblogs on the Internet provide an RSS feed of its content. I've seen different explanations about what RSS stands for. This was taken from the RSS specification:

“RSS is a Web content syndication format. Its name is an acronym for Really Simple Syndication. RSS is dialect of XML.”

But some people say "RDF Site Summary", and RDF stands for Resource Description Framework, which is a foundation for processing metadata. It really doesn't matter, it's a great way to publish content in a simple XML way.

RSS has been around since 1999 and I’ve tried to create a very simple RSS feed by reading the RSS 2.0 Specification located at http://backend.userland.com/rss.

Just for the “fun” of it, I tried to use XSL Transformation to turn the weblog XML file into the correct RSS format. So, I created a new WebForm ASPX page, and removed everything except the Page header from it, and added a ContentType attribute to it for text/xml.

ASP.NET
<%@ Page contenttype="text/xml" Language="vb" AutoEventWireup="false" 
Codebehind="rss20.aspx.vb" Inherits="Weblog.rss20"%>

Then I drag/dropped an ASP.NET XML Control to the page and added some code in code-behind to point out the XML file and the XSL file.

VB.NET
Private Sub Page_Load(ByVal sender As System.Object, _
                      ByVal e As System.EventArgs) Handles MyBase.Load
    Dim doc As XmlDocument = New XmlDocument()
    doc.Load(ConfigurationSettings.AppSettings("xmlFile"))

    Dim trans As XslTransform = New XslTransform()
    trans.Load(ConfigurationSettings.AppSettings("RSSxslFile"))

    Xml1.Document = doc
    Xml1.Transform = trans
End Sub

This is the XSL file used to transform the XML file:

XML
<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:template match="/">
    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
      <channel>
        <title>My weblog</title>
        <link>http://localhost/weblog/</link>
        <description>Just another weblog...</description>
        <managingEditor>someone@somewhere.com (My Name)</managingEditor>
        <language>en-us</language>
        <xsl:for-each select='weblog/logentry'>
          <item>
            <link>http://localhost/weblog/Item.aspx?id=<xsl:value-of 
            select='id'/></link>
            <guid isPermaLink="false"><xsl:value-of select='id'/></guid>
            <title><xsl:value-of select='logtitle'/></title>
            <description><xsl:value-of select='logtext'/></description>
            
            <pubDate><xsl:value-of select='logtimeGMT'/></pubDate>
          </item>
        </xsl:for-each>
      </channel>
    </rss> 
  </xsl:template>
</xsl:stylesheet>

The XSL file loops through each log-entry and writes them out within description and pubDate tags. Publication date needs to be in RFC 822 format (GMT-format) according to the RSS spec, that’s why I use that field in the XML file.

Update: The XSL file has been updated now so it also writes out a title, guid and a link to the blog entry.

One bad thing with this page is that it will write out every record in the weblog, something I took care of in the web page. It shouldn’t be too hard to sort and filter out the top 50 records or so in the way it’s done in the web page, but I leave that for later updates.

Points of Interest

I could have created the RSS feed in a number of different ways, but I’ve always wanted to try out the ASP.NET XML Control, so that’s why I went for that. I found out that you can do a number of things with XSL Transformation, but wow, it’s pretty complicated.

As I wrote earlier in the article, it’s easy to forget that the .NET Cache keeps pointers to reference type objects and if you change data in objects you get from the Cache, you are actually changing the object kept in the .NET Cache. Remember that when using the Cache object, you might mess things up for other visitors to the webby. As long as you store value types in the Cache, you don’t have to worry.

Updates

Update 1 - I added a title to the blog entry, mostly because it looks best in different RSS readers if there's a title for each blog entry. For the sake of RSS, I also added a guid for each entry. I also added code to transform some of the formatted text in the RichTextBox into HTML. It shouldn't be any problems to cut and paste colored and indented source code or HTML/XML into the RichTextBox. It looks pretty good on the HTML page. Note that it doesn't handle hyperlinks yet.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Web Developer
Sweden Sweden
Johan started coding a long time ago on a ZX Spectrum and the Z80 processor. His parents were rather sceptical about him sitting up all night coding and playing Manic Miner but they don't regret buying that computer now, since it turned out pretty good in the end.
Johan is now working as a Systems Architect and jump between a number of different platforms and computer languages every day. He wouldn’t cope if it weren’t for IntelliSense and code completion...

Comments and Discussions

 
GeneralRe: Use XSLT or not? Pin
nidhogg8-Jul-03 21:43
nidhogg8-Jul-03 21:43 
GeneralRe: Use XSLT or not? Pin
AndrewCherry30-Dec-03 13:23
AndrewCherry30-Dec-03 13:23 
GeneralHandling Links Pin
madajczyk13-Jan-03 5:05
madajczyk13-Jan-03 5:05 
GeneralRe: Handling Links Pin
John Bates13-Jan-03 20:00
John Bates13-Jan-03 20:00 
GeneralRe: Handling Links Pin
Johan Danforth13-Jan-03 22:07
Johan Danforth13-Jan-03 22:07 
GeneralRe: Handling Links Pin
madajczyk14-Jan-03 4:44
madajczyk14-Jan-03 4:44 
GeneralRe: Handling Links Pin
Johan Danforth14-Jan-03 22:22
Johan Danforth14-Jan-03 22:22 
GeneralRe: Handling Links Pin
Jarrett Vance17-Jun-03 4:17
Jarrett Vance17-Jun-03 4:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.