Here is the code for the MessageBoardDataContext
class:
Collapse public class MessageBoardDataContext : DataContext { public MessageBoardDataContext() : this(_connectionString) { } public MessageBoardDataContext(string connectionString) : base(connectionString, _mappingSource) { } static string _connectionString = ConfigurationManager.ConnectionStrings[ "LocalSqlServer"].ConnectionString; static XmlMappingSource _mappingSource = GetMappingSource(); private static XmlMappingSource GetMappingSource() { return XmlMappingSource.FromStream( typeof(MessageBoardDataContext) .Assembly .GetManifestResourceStream( "MessageBoard.DataAccess.Linq.Mapping.xml")); } public Table<Message> Messages { get { return GetTable<Message>(); } } }
As we already discussed LINQ to SQL has two different ways to map properties and fields in classes to columns in tables. The first one is via attributes specified on the properties and the classes and the second is through XML file. LINQ to SQL has a general purpose abstract base class called MappingSource
to handle mapping. Currently two concrete implementations of this class are: AttributeMappingSource
and XmlMappingSource
. The DataContext
class has a constructor that takes a MappingSource
. In the above code snippet we create an XmlMappingSource
from the Xml file stored in the assemblies resource. This is done in the GetMappingSource
method by calling XmlMappingSource.FromStream
and passing it the manifest resource stream.
That's all! The MessageBoardDataContext
uses the XML mapping we supplied to map the Messages
table to the Message
class and we are able to use LINQ to SQL. The advantage of using XML file for mapping is that it does not clutter the actual code with LINQ to SQL specific attributes. The other advantage is that the business layer classes can be designed independently of LINQ to SQL.
One final thing I want to cover before we move on to the presentation layer of the Message Board, is the adding new messages to the database. Here is the implementation of the AddMessage
method:
public int AddMessage(string subject, string text, string postedBy, string postedById, DateTime datePosted) { using (MessageBoardDataContext context = CreateDataContext()) { context.ObjectTrackingEnabled = true; Message message = new Message(); message.Subject = subject; message.Text = text; message.PostedBy = postedBy; message.PostedById = postedById; message.DatePosted = datePosted; context.Messages.InsertOnSubmit(message); context.SubmitChanges(); return message.Id; } }
After creating the DataContext
object the ObjectTrackingEnabled
property is set to true. What this means is that data context keeps track of objects to figure out if they have been updated or needs to be inserted. (We need to do this because CreateDataContext
sets the property to false.)Next we create a Message
object and assign all its properties, except the Id
property. The we call InsertOnSubmit
which indicates to the data context that a particular Message
object
has to be inserted in the database when the SubmitChanges
method is called. The SubmitChanges
makes a batch call to the database sending all the updates (if any) and inserts. At the end of SubmitChanges
the message object is inserted in the database. Not only that, the Id
property of the Message
object is automatically populated from the database table's identity value. This is because of the following line in the XML file:
<Column Name="Id" Member="Id" DbType=" Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" AutoSync="OnInsert" />
The AutoSync="OnInsert"
and IsDbGenerated="true"
attributes indicates that a specific property is an identity property and needs to be automatically loaded after insert. Doing so causes the following insert statement to be generated by LINQ to SQL:
INSERT INTO [Messages]([Subject], [Text], [PostedBy], [PostedById], [DatePosted]) VALUES (@p0, @p1, @p2, @p3, @p4) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]
After the insert the SELECT CONVERT(Int,SCOPE_IDENTITY())
statement obtains the identity value inserted in the table.
Why CONVERT(Int,SCOPE_IDENTITY())?
The SCOPE_IDENTITY()
function returns a decimal
and since the Id
property is of type int
, LINQ to SQL generates the SQL query which uses the CONVERT
function.
We will revisit LINQ to SQL one more time when we will see how to create a new database using LINQ to SQL. Now let's move on to the presentation layer using ASP.NET.
The Presentation Layer
The presentation layer consists of an ASP.NET web site and a C# assembly. The web site consists of ASP.NET pages, style sheets and images. As far as possible the web site is coded declaratively. Any non-trivial code required to support the web site is placed in the MessageBoard.Web
project. The aim is to have unit tests all non trivial code, so that they can be tested properly for quality. The unit testing and the load testing will come as a part of separate article. It also helps in separating concerns: a designer can work on the web pages independently of the developer and vice versa. Personal preference has a lot to do too with partitioning projects in that way and so it is by no means the way to partition projects. As we move along in the next few articles, we will add ASP.NET server controls to the MessageBoard.Web
project.
Let's start with the Web.Config
file. For maximum performance it is better to turn off the view state and the session state in all the pages. Don't get me wrong, view state and session state have their place in developing web sites, but in the message board site they will not be needed. So we add the following configuration entry:
<configuration> <system.web> <pages enableViewState="false" enableSessionState="false" >
Let's look at the web site map:

The site has a master page named Site.master
which contains things like the header and the navigation bar. All pages in the web site use the same master page. The main page is Default.aspx
which shows list of all the messages with subject, user, date posted and partial text. When the user clicks on any of the message he is taken to the Message.aspx
page which shows the full details of the message. The site has Login.aspx
page which a user can use to login to the site and a Register.aspx
page which he can use to register. The Login.aspx
page and the Register.aspx
page make use of the ASP.NET Login
and the CreateUserWizard
control. The Settings.aspx
page is where the user can change the settings such as his time zone. Feed.svc
is a web service that provides RSS and ATOM feeds. The site has CSS files corresponding to the two themes: Outlook and Floating. The site uses CSS for layout and positioning so except for the standard ASP.NET controls which use tables for layout you will not find any tables on the site. Later on, we can use the ASP.NET CSS Friendly control adapters to remove the remaining tables.
The Master Page
Look at the following screen shots of Default.aspx
and the Message.aspx
pages.
Default.aspx:

Message.aspx:
You will notice that the top banner and the navigation panel with the theme selector on the left are the same. These common elements have been put in the site's master page: site.master
so they appear on every page. While putting links an banner in the master page is quite common and nothing complex, the tricky part is to put the theme selector on the master page. The problem is that a web page theme can only be changed in the Page's PreInit
event and the master pages do not get applied until after it. In fact the master page is applied after the theme is set.
In order for the theme selector to work properly with the master page we have to rely on the Global.asax
file to change the theme as shown below:
void Application_PreRequestHandlerExecute(object sender, EventArgs e) { Page page = Context.Handler as Page; if (page == null) return; ... page.PreInit += delegate { page.Theme = theme; }; }
The PreRequestHandlerExecute
execute function is called just before ASP.NET starts the page life cycle. The page object is instantiated but it's life cycle has not started yet. At this point we handle the PreInit
event of the page and set the theme appropriately.
How we obtain the theme is a little complex because of the way master pages work. Normally, when you have no master pages you can safely assume that controls which are on the form have same ID in the client as on the server. However with master pages it's no longer true. ASP.NET generates a client ID based on where the page appears in control hierarchy. Take a look at the following control:
<asp:DropDownList runat="server" class="ThemeSelector" ID="ThemeSelector" AutoPostBack="true" />
We cannot assume that the client side ID of the above control will be ThemeSelector
. This is because it is on a master page. So the client ID might be something like ctl000_ThemeSelector
. So to access the value during post back is a two step process.
First, we need a way to obtain the client id of the control. This is done by adding a hidden field to the form which contains the unique ID of the control. The unique ID of the control is the name by which it's post-back value can be extracted from the Request.Forms
collection. So the following code in the master page ensures that the hidden field contains the unique id of the ThemeSelector
drop down list.
protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); .... this.Page.ClientScript.RegisterHiddenField("ThemeSelectorId" , ThemeSelector.UniqueID); }
So the value of the selected theme in the drop down list can be obtained from the following code in the Global.asax
PreRequestHandlerExecute
event handler:
string themeSelectorId = Request["ThemeSelectorId"]; string theme = Request[themeSelectorId];
The first line looks up the id of the control an the next line obtains the post-back value (which will be the selected value in the drop down list). Finally, the theme is applied correctly in the Pre_Init
event. That's the only code in the site.master
page worth mentioning. Now, let's move on to the main
page (Default.aspx
).
The Main Page
The main page is where we get a chance to use the exciting new control in ASP.NET 3.5: the ListView
control. The ListView
control is another data bound control in the family of GridView, Repeater
and the DataList
controls. The nice thing about it is that it combines the good features of all these controls. The following table compares list view with other controls:
| ListView | GridView | Repeater | DataList |
Paging Support | Yes | Yes | No | No |
Flexible Layout | Yes | No (only tabular layout possible) | Yes | No (Layout uses tables) |
Editing support | Yes | Yes | No | Yes |
Insertion support | Yes | No | No | No |
The ListView
control thus has the good features of GridView, Repeater
and the DataList
controls. The best thing about the ListView
is that it offers lot of control on the generated HTML. Therefore, it is possible to generate a clean HTML well suitable for CSS layouts. This however does not mean that ListView
is best for all data binding scenarios. For displaying tabular data, I still think that GridView
is the best. However, I do find it hard to come up with scenarios where Repeater
and DataList
are better than the ListView.
If you can think of one please be free to post it in as a comment. Now that I have built lot of expectations about ListView,
let's see if it meets the expectations.
Using ListView Control to Dipslay and Insert data
The list view is a Data bound control it can bind to any data source supported by ASP.NET. For the message board we have to use the object data source control as we have data available through the MessageSource
type. Remember that message board API consumers are unaware of the way data is stored. The ASP.NET ObjectDataSource
control comes in pretty handy to expose the message board data accessed via MessageSource
class to data bound controls in a declarative fashion.
<asp:ObjectDataSource ID="messageDataSource" runat="server" TypeName="MessageBoard.MessageSource" SelectMethod="GetRecentMessages" StartRowIndexParameterName="start" MaximumRowsParameterName="count" SelectCountMethod="GetMessageCount" EnablePaging="True" InsertMethod="AddMessage">
The ObjectDataSource
can get and save data from a business object by calling methods on the business objects. We indicate the type name of the business object using the TypeName
property of the
ObjectDataSource
control.
In our case it will be the MessageSource
class which is our only interface to the Message board. We indicate the method GetRecentMessages
as the one which will be responsible for providing data. The signature of the GetRecentMessages
look like the following:
IEnumerable<Message> GetRecentMessages(int start, int count)
The start
parameter indicates the index first message to obtain from the list of all messages and the count
parameter indicates the maximum number of messages to obtain. Thus, the
StartRowIndexParameterName
has been set to start and the MaximumRowsParameterName
has
been set to count.
The ObjectDataSource
control automatically uses these properties to automatically page data at the source. Also notice the SelectCountMethod
which is set to GetMessageCount
. The ObjectDataSource
calls this method to estimate the maximum number of messages available for paging purposes. Finally, we set the InsertMethod
property to AddMessage
. This method will be responsible for adding messages to the message board.
The ListView
control can be bound to the data source using the following markup:
<asp:ListView ID="messageListView" runat="server" DataSourceID="messageDataSource" ..>
Now that the list view is bound to the data source, the liist view can generate individual items from the IEnumerable<Message>
object returned by the GetRecentMessages
method. ListView
is a very flexible control it allows you to control all aspects of the layout including the root HTML element which will contain the items. Let's see how we specify the markup to generate in the list view ocntrol.
When designing a web page I normally start with a raw HTML page that will resemble the output of the ASP.NET web page and then generate the markup for the ASP.NET page. The HTML code which I came up with looks like the following:
<div class="header"> <span class="subject">Subject</span> <span class="postedBy">Posted By</span> <span class="datePosted">Date Posted</span> </div> <div id="messageList"> <div class="message" > <h2 class="subject"><a> ... </a></h2> <div class="postedBy"> <b>Posted By: </b>...</div> <div class="datePosted"> <b>Date Posted: </b> ...</div> <div class="text"> ... </div> </div> <div class="message" >... </div> </div>
So basically we have a div
with id of messageList
and with-in which we have all the message items. To get such an output using the ListView
control we have to take the following steps.
First, we have to specify the LayoutTemplate
of the ListView
control as following:
<asp:ListView ...> <LayoutTemplate> <div class="header"> <span class="subject">Subject</span> <span class="postedBy">Posted By</span> <span class="datePosted">Date Posted</span> </div> <div id="messageList"> <asp:PlaceHolder runat="server" ID="itemPlaceHolder" /> </div> </LayoutTemplate> ... </asp:ListView>
Of particular interest is the PlaceHolder
control whose ID
is itemPlaceHolder
. The
ListView
control replaces the place holder with the rendered HTML for each individual items in the data source. Now we need to specify how a particular item in the data source should be rendered in HTML. This is done by specifying the ItemTemplate
of the ListView
as shown below:
<asp:ListView ...> <ItemTemplate> <div class="message"> <h2 class="subject"> <a href='<%# MessageUrl %>'> <%# Message.Subject %> </a> </h2> <div class="postedBy"> <b>Posted By: </b><%# Message.PostedBy % ></div> <div class="datePosted"> <b>Date Posted: </b> <%# MessageDateInUsersTimeZone %> </div> <div class="text"> <asp:Literal runat="server" Text='<%# MessagePreviewText %>' Mode="Encode" /> </div> </div> </ItemTemplate> ... </asp:ListView>
Notice the ASP.NET data binding expressions. If you are accustomed to using Data Binding expressions you will observe the lack of Eval
function. To make the code cleaner and also avoid reflection when using Data Binding, I have properties declared as following in the page class.
private MessageBoard.Message Message { get { return Page.GetDataItem() as MessageBoard.Message; } } private string MessageUrl { get { return "Message.aspx?id=" + Message.Id.ToString( CultureInfo.InvariantCulture); } } private string MessageDateInUsersTimeZone { get { return Utility.GetFormattedTime(Message.DatePosted); } } private string MessagePreviewText { get { return Utility.GetPreviewText(Message.Text)); } }
The Message
property needs a little explanation. The Page.GetDataItem
method returns the current item that is being data bound. Thus from within the ItemTemplate
, the GetDataItem
will return a Message
object. Thus the Message
will return the current Message
object that is being data bound.
When this property is accessed outside of a data binding context an exception will be thrown.
Paging ListView using the DataPager Control
Unlike, GridView
the ListView
control does not have any way to specify the template for the pager controls. Instead, the ListView
control implements an interface named IPageableItemContainer
. Any control that implements this interface can be paged using the new DataPager
control. So in order to get the paging to work, we need to drop a DataPager
control and set its properties:
<asp:DataPager ID="topPager" runat="server" PagedControlID="messageListView" QueryStringField="start" PageSize="25">
We first set the PagedControlID
property and assign it the id of the ListView
control. We also set the PageSize
property that indicates the maximum number of items in the page. In future versions we will load the PageSize
from user settings. The final thing to note here is the property named QueryStringField
whose value is set to start
. The real beauty of the DataPager
control is that it can automatically use the value of this query string field (start
) to move the control to a specific page. This saves us from writing any imperative code.
Finally, you can customize the pager controls in variety of ways. You can indicate how you want it to appear: numeric, next/previous buttons or custom or a combination of all. The following code shows how to get a pager with both next/previous buttons and the numbers.
<asp:DataPager ID="topPager" runat="server"> <Fields> <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" ShowNextPageButton="False" ShowPreviousPageButton="True" FirstPageText="<<" LastPageText=">>" NextPageText=">" PreviousPageText="<" RenderDisabledButtonsAsLabels="false" /> <asp:NumericPagerField /> <asp:NextPreviousPagerField ButtonType="Button" ShowLastPageButton="True" ShowNextPageButton="True" ShowPreviousPageButton="False" RenderDisabledButtonsAsLabels="false" NextPageText=">" LastPageText=">>" /> </Fields> </asp:DataPager>
The above code generates a pager that looks like the following:

We have seen how to display and page data in the list view, now let's move on to inserting data: Posting a new message.
Inserting Data using the ListView Control
The greatest advantage of ListView
control is that not only it can display data but it also has support for inserting and editing data. In case of message board we will not be editing data but we will sure be inserting data as we allow users to post messages. Instead of developing a separate page or using a different control like the FormView
control, we can directly use the ListView
control to insert data. Recall that in the declaration of the ObjectdataSource
control, we set the property InsertMethod
to "AddMessage"
. This indicates that the ObjectDataSource
control should call AddMessage
when it is requested to insert new data. Who exactly requests the ObjectDataSource
to insert new data? That will be any data bound control bound to the ObjectDataSource
with support for inserting data. In our case it is the ListView
.
To enable a ListView
to insert data, we need to do two things. First, we need to set the the InsertItemPosition
property to either "LastItem
" or to "FirstItem
". This controls where exactly the ListView
will display a panel with editable controls which a user can use to insert data. Next, we need to define the InsertItemTemplate
and put editable data bound controls in it:
Collapse <asp:ListView InsertItemPosition="LastItem" ... > ... <InsertItemTemplate> <div id="newMessagePanel"> <a id="newMessageBookmark"></a> <h2> Post a Message</h2> <div id="subjectPanel"> <asp:Label CssClass="subjectLabel" runat="server" AccessKey="S" Text="Subject:" /><br /> <asp:TextBox ID="Subject" CssClass="subjectTextBox" runat="server" Text='<%# Bind("Subject") %>' Columns="60" Rows="1" /> </div> <div id="textPanel"> <asp:Label CssClass="textLabel" runat="server" AccessKey="T" Text="Text:" /><br /> <asp:TextBox ID="Text" CssClass="textTextBox" runat="server" Text='<%# Bind("Text") %>' TextMode="MultiLine" Rows="10" Columns="60" /> </div> <div id="buttonPanel"> <asp:Button ID="PostMessage" CommandName="Insert" runat="server" Text=" Post Message" /> <asp:Button ID="Cancel" runat="server" CommandName="Cancel" Text="Cancel" CausesValidation="False" /> </div> </div> </InsertItemTemplate> </asp:ListView>
The figure below shows how the ASP.NET markup shown above renders (without any styles):

The important points to observe here is how the subject and text fields are bound. The Text
property of Subject is set to Bind("Subject")
and that of text field is set to Bind("Text")
. Where are we getting the strings that we pass to the Bind
method? The answer lies in the signature of MessageSource.AddMessage
.
public static void AddMessage(string subject, string text)
The the parameter passed to Bind
(this is not a method or a function but just a special word understood by ASP.NET data binding engine), corresponds to the parameter names of AddMessage
. The other important thing to note here is the CommandName
property of the
Post Message button. This indicates the when the post message button is pressed the ListView
should data bind and insert the data. If you don't specify the command name as insert the ListView
will not be able to insert data.
The message detail page (Message.aspx) also uses a ListView
control. I will skip the details here as it is very similar to the main page. I will also skip through the Logon and Registration pages which use the standard asp.net Login controls. We will move to the settings page and see how the site manages time zones.
Managing Time zones in the Message Board
If your website is on internet and catered to a global audience, you have to address the issue of time zone when displaying date and time. For example, in the message board site the users are displayed messages along with the date and time when the message was posted. The issue here is what date and time should be shown to the user. Here are a few options:
- Display the data and times in the time zone of the server machine. This will not make much sense to the users who are not in the same time zone as the web server's time zone. Plus the users need to know the server time zone.
- Display all times in GMT or UTC. This option has disadvantage that it requires the users to translate the times from GMT/UTC to their own time which is not a very user friendly option
- Displays the time span instead of displaying the actual time. It shows how many days, hours and minutes ago a particular forum post was made. This works fine but sometimes it is not very intutive.
- The option employed in the message board is to show the date and time in time zone of the user accessing the web site. This option makes the most sense to the user however it requires some bit of programming. Fortunately, working with Time zones is a lot easy with the new
TimeZoneInfo
class introduced in .NET Framework 3.5.
In the message board web site the users are given option to specify a time zone in which they want to view the date and time of posted messages. This is done in the Settings page where the user is provided with a drop down to select a time zone.

The Time Zone drop down list displays all the available time zones. This list can be obtained by using the services of the TimeZoneInfo
class. The TimeZoneInfo
class provides a static method named GetSystemTimeZones
which returns an array of TimeZoneInfo
object. Using the ObjectDataSource
control a combo box can be bound to the values returned by the TimeZoneInfo
class as shown below:
<asp:DropDownList runat="server" ID="TimeZoneList" CssClass="TimeZoneList" DataSourceID="TimeZoneSource" DataTextField="DisplayName" DataValueField="Id" AppendDataBoundItems="true"> <asp:ListItem Text="Universal Time" Value="UTC" />; </asp:DropDownList>; <asp:ObjectDataSource runat="server" TypeName="System.TimeZoneInfo" SelectMethod="GetSystemTimeZones" ID="TimeZoneSource" />
We bind the text to the DisplayName
and the value to the time zone Id
. A time zone can be uniquely identified using a string Id. The TimeZoneInfo
class provides a method called FindSystemTimeZoneById
for this purpose. Notice that we do have to add the UTC time zone separately as it is not returned in the list of time zones. This is the default time zone for any user who has not configured the time zone. Once the user saves changes to his settings the selected time zone id is saved to a cookie. This is done in a method called SaveTimeZone
in a class called TimeZoneUtility
.
class TimeZoneUtility { ... public static void SaveTimeZoneInfoInCookie(TimeZoneInfo info) { HttpContext context = HttpContext.Current; if (context == null) throw new InvalidOperationException(Resources.NullHttpContext); HttpCookie cookie = new HttpCookie(CookieName, info.Id); cookie.Expires = DateTime.Now.AddYears(1); context.Response.AppendCookie(cookie); } }
This method is invoked from the settings page when the user saves the settings:
protected void SaveChanges_Click(object sender, EventArgs e){ TimeZoneUtility.SaveTimeZoneInfoInCookie( TimeZoneUtility.GetTimeZoneFromId(TimeZoneList.SelectedValue)); Response.Redirect("~/Default.aspx"); }
The time zone info can be retrieved from the cookie using the following code:
public static TimeZoneInfo GetTimeZoneInfoFromCookie() { HttpContext context = HttpContext.Current; if (context == null) throw new InvalidOperationException(Resources.NullHttpContext); HttpCookie cookie = context.Request.Cookies[CookieName]; TimeZoneInfo info = TimeZoneInfo.Utc; if (cookie == null || String.IsNullOrEmpty(cookie.Value)) return info; try { info = TimeZoneInfo.FindSystemTimeZoneById(cookie.Value); } catch (TimeZoneNotFoundException ex) { Trace.WriteLine(ex); } return info; }
The above function extracts a time zone from the cookie if present or otherwise it returns TimeZoneInfo.Utc
which is the default. To display date and time information in users time zone, there is a separate function named GetFormattedTime
which returns a formatted date time value in the users time zone.
public static String GetFormattedTime(DateTime dateTime) { return TimeZoneUtility.ConvertToCurrentTimeZone(dateTime) .ToString("MMMM dd, MM:hh tt"); }
Finally, the TimeZoneUtility.ConvertToCurrentTimeZone function extracts the time zone from a cookie and converts a specified date time to users time zone:
public static DateTime ConvertToCurrentTimeZone(DateTime dateTime) { return TimeZoneInfo.ConvertTimeFromUtc(dateTime, TimeZoneUtility.GetTimeZoneInfoFromCookie()); }
Thus the TimeZoneInfo
class comes in pretty handy when working with time zones. It is a late but welcome addition to .NET Framework. Now let's move on to another new feature of .NET Framework 3.5: WCF Syndication API and WCF Web Programming Model.
Displaying RSS and ATOM Feeds to the User
Providing RSS or ATOM feeds is becoming a necessary feature of any web site. Of course, it makes lot of sense for the message board site to do so. When it comes to providing feeds I could have used LINQ to XML and hand crafted something. However, WCF in .NET 3.5 provides an API to generate and parse RSS and ATOM feeds. This is a part of the assembly System.ServiceModel.Web and the classes are in System.ServiceModel.Syndication
namespace. Why this is a part of WCF? I have no idea but it is a welcome addition nonetheless. The advantage of using the WCF Syndication API over hand crafting something is that you don't have to go into the details of the specs of each of the feed formats. Further, you can use the same code to generate both RSS and ATOM feeds.
In the MessageBoard web site, we use the syndication API in conjunction with the WCF Web programming model. Let's pause briefly to discuss about WCF web programming model. Typically, when you invoke WCF service calls you have to construct SOAP messages and send them to the service and the service responds back with another SOAP message. With web programming model you can issue simple HTTP GET or HTTP POST requests to invoke WCF service calls. This is a lot simpler than constructing SOAP messages. Let's see how the web programming model works for the Message Board.
First, we need to create a service contract:
public enum FeedFormat { Atom, Rss } [ServiceContract] public interface IFeedService { [OperationContract] [WebGet] [ServiceKnownType(typeof(Rss20FeedFormatter))] [ServiceKnownType(typeof(Atom10FeedFormatter))] SyndicationFeedFormatter GetLatestMessages(FeedFormat format); }
The GetRecentMessages takes an enum
parameter of type FeedFormat
which can be Atom
or Rss
. Given the format of the feed it returns the feed of that format. Let's go through each of the attributes on the method one by one:
- The
OperationContract
attribute ensures that the particular interface method can be invoked via WCF. - The
WebGet
attribute ensures that the method can be accessed via plain HTTP GET request. - The two
ServiceKnownType
attributes ensure that the return value SyndicationFeedFormatter
can either be a instance of Rss20FeedFormatter
or Atom10FeedFormatter
.
If a WCF method outputs an RSS or an ATOM feed, the return value of the method should be SyndicationFeedFormatter
(or one of it's sub classes). WCF will serialize SyndicationFeedFormatter
object output the raw RSS or the ATOM feed.
Next, we need to implement the interface is a class.
public class FeedService : IFeedService
Here are the steps to use syndication API to return a feed:
- Create a SyndicationFeed object
- Populate the Uri, Description, Title and other properties of
SyndicationFeed
. - Create a collection of
SyndicationFeedItem
s which will represent each individual message in the feed and assign the collection to the Items
property of the SyndicationFeed.
Let's see these steps as implemented in the FeedService
.
public SyndicationFeedFormatter GetLatestMessages(FeedFormat format) { Uri rootUri = GetRootUri(); SyndicationFeed feed = new SyndicationFeed( Resources.MessageBoard , Resources.MessageBoardDescription, rootUri ); feed.Items = from m in MessageSource.GetRecentMessages(0, 10) select CreateSyndicationItem(m, rootUri); if(format == FeedFormat.Atom) return new Atom10FeedFormatter(feed); return new Rss20FeedFormatter(feed); }
First we call a method named GetRootUri
. This method gives the root URI of the web application. For example, if the application is deployed on localhost in a virtual directory named MessageBoard
. The value returned by the GetRootUri
will be: http:
. Once we get the root URI we create a syndication feed with a title, description and the root URI. The title and description are loaded from the resource file. Finally we use LINQ to Objects, to convert a collection of recent 10 messages to a collection of SyndicationFeedItem
s. The method finally returns an appropriate type of SyndicationFeedFormatter
depending on the value of the format parameter. This is done using a helper function called CreateSyndicationItem
.
private SyndicationItem CreateSyndicationItem(Message m, Uri rootUri) { UriBuilder uriBuilder = new UriBuilder(rootUri); uriBuilder.Path += "Message.aspx"; uriBuilder.Query = "id=" + m.Id.ToString( CultureInfo.InvariantCulture); var item = new SyndicationItem(m.Subject, m.Text, uriBuilder.Uri, m.Id.ToString(), new DateTimeOffset(m.DatePosted, new TimeSpan(0))); item.Authors.Add(new SyndicationPerson(m.PostedBy)); return item; }
In the above function, we construct the URI using UriBuilder
. This is the URL or the permalink of the at which a particular message will be available. Then we create a SyndicationItem
with the information from the Message
object. The SyndicationItem
takes an object of type DateTimeOffset
which represents date and times in offsets from UTC.
Interestingly, DateTimeOffset
class is in mscorlib.dll. This is a new class introduced in .NET 2.0 SP1 which means that it is automatically available in .NET 3.5. This is in contrast to the TimeZoneInfo
class which is present in System.Core.dll. This further complicates the entire .NET 2.0 - .NET 3.5 saga.
Now we have a service contract and an object which implements the service contract. The service is exposed via Feed.svc
file in the Message Board web site. The contents of Feed.svc
are shown below:
<%@ ServiceHost Language="C#" Debug="true" Service="MessageBoard.Web.FeedService" %>
This make sure the our service is available through the Feed.svc
file in the web site. We are not done yet, we need to apply the WCF configuration in the web.config file to expose the service using the web programming model. This is done in the configuration file as shown below:
<system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="feedHttp"> <webHttp /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="FeedServiceBehavior"> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors> <services> <service behaviorConfiguration="FeedServiceBehavior" name="MessageBoard.Web.FeedService"> <endpoint address="" behaviorConfiguration="feedHttp" binding="webHttpBinding" contract="MessageBoard.Web.IFeedService" /> </service> </services> </system.serviceModel>
Important things to note here is that the service uses webHttpBinding
and the endpoint behavior includes webHttp
. Bothe of these are necessary for service to be accessed via web programming model. Finally, when you can access the feeds by typing in the appropriate URLs as shown in the screen shot below:

WCF web programming model is pretty nice and we will revisit it in the later parts in the series. With this we have a complete message board application, now we will see a little detail about the site layout and themes.
Themes and Layouts
The message board site relies heavily on CSS for layout and formatting. Here is how the main page looks without any CSS applied:

The site supports tow different themes: Outlook and Floating. The only difference between the themes is the CSS file behind the web pages. The HTML content of the site always remains the same. Here is how the site looks in outlook theme:

The theme tries to simulate Outlook 2007 Silver theme as much as possible. The background gradients are all made possible by using background images. As you will remember that the site does not use any HTML tables at all, the tabular layout which you see above is made possible by using a combination of relative positioning, absolute positioning, padding and margins. The subject column automatically resizes with window where as the Posted By and Date Posted columns stay constant.
Finally, here is how the site looks in Floating theme:

There is a custom background image on each of the message and the messages have the float
CSS attribute set to left
. The site also uses image replacement techniques to replace the heading Message Board with a custom image. This is done by adding a background image and hiding and indenting the contents so that they don't appear. Another interesting thing about the floating theme is that only the messages scroll the control bar on the left and the banner on the top stay fixed. This is done via fixed CSS positioning.
CSS is amazingly powerful and it has improved a lot with Internet Explore 7.0. The advantage of using CSS for layouts is that it helps keep the HTML clean. The clean HTML is extremely useful when using AJAX in the web site. We will see in part III of the series how we can add AJAX support to the message board web site.
Installation Instructions
The site needs either SQL Express or a full fledged SQL Server 2005.
If you have SQL Server Express, follow the following steps:
- Open the solution file in Visual Studio 2008.
- Build the project
- If you have a custom instance of SQL Express which is not named SQLExpress, you need to modify the connection string settings web.config file.
<configuration> <connectionStrings <add name="LocalSqlServer" connectionString="data source=.\SQLEXPRESS;...." providerName="System.Data.SqlClient"/>
Modify the data source to use the name of the custom instance. Leave the rest if the connection string. Note: I have not included the full connection string here.
- Right click on the file
Install.ashx
which is in the message board web site project and click on view in browser.
- This will launch the browser and automatically create the database.
If you have SQL Server, follow the following steps:
- Open the solution
- Build the solution
- Open Install.sql file MessageBoard web site project. This file is in the Install folder.
- Right click and click execute. Select the database connection and click ok. This will install the messages table, ASP.NET sql services and sample data.
- Now you need to change the web.config file to use the new connection string.
<configuration> <connectionStrings <add name="LocalSqlServer" connectionString=Modify the connection string providerName="System.Data.SqlClient"/>
About the Install Scripts
To generate the database install script, I used a feature of Visual Studio 2008 which I accidentally came across. When you right click on a data source in the server explorer, and option named Publish to Provider appears:

Selecting this option launches a wizard that generates script for the entire database both for schema and the data. That is how install.sql file was generated.
The Install.ashx file uses LINQ to SQL for creating a database. Here is the code snippet that does it:
MessageBoardDataContext dataContext = new MessageBoardDataContext(connectionString); if (!dataContext.DatabaseExists()) { dataContext.CreateDatabase(); response.Write("Adding ASP.NET Services.... "); response.Flush(); SqlServices.Install(dataContext.Connection.Database, SqlFeatures.All, connectionString); response.Write("Installing sample data...."); response.Flush(); string sampleDataSqlFile = context.Request .MapPath("~/Install/InstallSampleData.sql"); dataContext.ExecuteCommand( File.ReadAllText(sampleDataSqlFile)); response.Write("Database created successfully!"); } else { response.Write("Database already exists"); }
The DataContext
class provides a method called DatabaseExists
, which given a connection string can figure out whether the database exist. We first use this method to check if the database exist and then if it does not we call the CreateDatabase
method. The CreateDatabase
method automatically uses the information specified in the ORM mapping and creates the database and the tables. After creating the database we call the SqlServices.Install
method to install the ASP.NET membership specific schema on the database.
Next in the Series
I have planned for the next few parts in this series as follows. I will provide the links once the articles are published.
- Part II - Posting Messages using Microsoft Word.
- Part III - Ajaxifying the Message board
- Part IV - Adding tags and Threaded discussions
- Part V - Load Testing, Caching and Performance Analysis of the Message Board
Acknowledgements
- My wife Radhika for writing the Non-Linq version of the
IMessageProvider
. - VuNic for quickly developing a background image for the message board site.
History
- December 21, 2007 - First posted
- December 31, 2007 - Updated to Series Navigation