Overview of the Sample Website
The
primary purpose of the website is to act as a code sample website for
.NET code. Anyone who visits the website can post a new code sample
entry. A code sample entry can consist of one or more code sample files.
The author of the code sample entry can provide a description of each
of the files. Furthermore, the author can add one or more tags to the
code samples to categorize and describe their purpose. Visitors to the
website can browse the existing code sample entries. They can rate code
samples when they view them. Furthermore, they can copy the code samples
so they can use the samples in their own applications.
The website
also includes a simple blog. The administrator of the website can post
blog entries about the code samples or about any other topic. Visitors
to the website can add comments to a blog entry. The website exposes
both an ATOM and RSS feed. If someone wants to subscribe to the blog,
that person can subscribe to either the ATOM or RSS feed. The home page
of the website displays a code cloud and a list of recent blog entries
(see Figure 1). The code cloud consists of a distinct list of all the
code entry tags. The more code entries that share a tag, the larger the
tag appears in the code cloud. In the figure, you’ll notice that a lot
of code samples are related to validation. If you click a tag in the
code cloud, you are transferred to a page that contains a list of code
samples associated with the tag.
Figure 1:
The home
page also contains a list of three recent blog entries. A summary of
each blog entry appears in the home page. You can click a blog entry to
view the full entry. In this section, you are provided with a
walkthrough (with lots of screen shots) of the process of adding both
blog entries and code entries.
Creating Blog Entries
Only
a member with the Administrators role can post new blog entries. If you
want to add a new blog entry, you must log in by clicking the Login
link that appears at the top of every page. I’m set up as the
administrator for the website by default. If you log in using the
username Stephen and password secret, you will be logged in as an
administrator. You can create a new administrator account (and delete
the Stephen account) by using the Website Administration Tool. After
opening the sample site in Visual Web
Developer,
select the menu option Website, ASP.NET Configuration. When the Website
Administration Tool opens, select the Security tab to manage users and
roles.
After you
log in, you can post a new blog entry by clicking the {Add Blog Entry}
link that appears under the current list of blog entries on the home
page. Clicking this link transfers you to the page displayed in Figure
2. When you create a new blog post, you complete the following fields:
Title—The title of the blog post.
Introduction Text—The
introduction text appears on the home page of the website as a summary
of a blog entry. The introduction text is also used by the ATOM and RSS
feeds.
Post—The full blog entry post.
Is Pinned—When this box is checked, the blog entry appears before all other blog entries on the home page.
Figure 2:
Notice that
the input field for entering a blog post uses a rich text editor. The
sample website uses the open source FCKeditor. You can download the
FCKeditor from www.fckeditor.net. The FCKeditor displays a rich editor
in the case of Internet Explorer and Firefox. A plain text editor is
displayed when the page is requested using the Opera browser. If you
attempt to submit the form without completing a required field,
validation errors are displayed using callouts (see Figure 3).
Validation errors are not displayed until you actually click the Next
button.
Figure 4:
After you
successfully complete the form for adding a new blog entry and click the
Next button, you are redirected to a page that you can use to tag the
new blog entry (see Figure 4). You can add as many tags to a blog entry
as you desire. The tags enable users to cross-navigate among related
blog entries. Blog entries that share the same tags are linked. The
TextBox for adding a new tag uses the AJAX Control Toolkit AutoComplete
Extender control. As you type, matching tags are retrieved from the
database. Using the AutoComplete extender makes it more likely that
you’ll use the same tags for multiple blog posts.
When you
are done adding all your tags, you can click the Finish button to finish
the process of adding a new blog entry. You are redirected to the
finished blog entry that the world sees.
After you
create a blog entry, you always have the option of editing or deleting
the entry. To edit or delete a blog entry, log in to the website using
an Administrator account, navigate to the blog entry page, and click
either the {Edit} or {Delete} link.
Creating Code Sample Entries
The
main purpose of the code sample website is to enable people to post new
code samples, browse existing code samples, and rate code samples. Any
registered user can post a new code sample entry at the website. Before
you can post a new code sample, you must navigate to the main code
sample page.
Click the Code Samples link that appears at the top of any page. You’ll see the page in Figure 5.
Figure 5:
The main
code sample page displays a list of the top ten highest rated code
samples, the top ten most viewed code samples, and the top ten most
recent code samples. At the bottom of the page, you can click the Add
New Code Sample link to add a new code sample. After you click the Add
New Code Sample link, you see the form in Figure 6. A code sample entry
can contain one more code samples. Typically, a code sample consists of
multiple files. The form in Figure 6 enables you to provide a
description of the entire code sample entry.
Figure 6:
After you
provide a description for the code sample entry and click Next, you are
redirected to a page that you can use to associate one or more code
samples with the code sample entry (see Figure 7). For example, you
might want to create both a VB.NET and C# version of a code sample.
Figure7:
If you
click the Add Code Sample link, you can add a new code sample. The form
for adding a new code sample contains the following fields:
File Name—The name of the code sample file (for example, SamplePage.aspx).
File Language—The programming language used for the code sample.
Description—The description of the code sample.
Code—The actual source code of the code sample.
Enable Try It—When
this box is checked, the code sample can be executed “live” from the
website. This field appears only for users in the Administrators role.
Try
It Code—The code executed when a code sample is executed “live” from
the website. This field appears only for users in the Administrators
role.
The last
two fields require some explanation. If you are a member of the
Administrators role, you can enable users to execute a code sample. When
you check the Enable Try It check box, a Try It link appears with the
code sample that a user can click to run the code sample (see Figure 8).
Because, most likely, you’ll want to use a different database
connection with the code that gets executed when a user clicks Try It,
there is a separate text field that you can use to enter the Try It
code. The source code entered into the Try It Code text field is never
displayed to the public.
After you
submit a code sample, you can click Next to move to a page that enables
you to tag a code sample entry. You can associate a maximum of three
tags with any code sample entry. The tags appear in the code cloud. They
also appear at the bottom of each code entry to provide visitors to the
website with a way to navigate between related code samples. Data
Access and Validation In this section, you learn how data access and
form validation are implemented for the sample website. Data access and
form validation are implemented by taking advantage of new features of
the .NET 3.5 Framework.
Figure 8:
Data Access and Validation
Using LINQ to SQL
All
data access performed by the sample website is performed using LINQ to
SQL. No explicit SQL code was written. Taking advantage of LINQ to SQL
enabled me to dramatically reduce the amount of time and code required
to build the website. Normally, when performing data access from a web
application, you need to write an entire data access layer to bridge the
divide between your application and your database. By taking advantage
of LINQ to SQL, I could avoid writing a data access layer. Instead, I
could concentrate on the real task that I needed to accomplish: writing
the data access queries. I used the Object Relational Designer to create
my LINQ entities (see Figure 9). I ran into one issue when using the
Designer. Several of the website database tables include a DateCreated
column. This column has a default value generated by the SQL GetDate()
function. However, the Object Relational Designer did not pick up on
this fact and I received errors when performing inserts and updates. To
work around this problem, I had to manually update the DateCreated
property for each entity in the Object Relational Designer. Within the
Object Relational Designer, you can select a property of an entity and
modify that property in the Properties window. In the case of the
DateCreated property, I had to assign the value True to the Auto
Generated Value property (see Figure 10).
Figure 10:
I created a
separate partial class for each entity. I added the LINQ to SQL queries
that I needed to the partial class. For example, Listing 1 contains
some of the code for the partial CodeSample entity.
LISTING 1 CodeSample.cs (Partial)
public partial class CodeSample : EntityBase<CodeSample>
{
public IEnumerable<CodeSample> SelectByEntryId(int entryId)
{
return Table.Where( s => s.EntryId == entryId );
}
partial void OnCreated()
{
this.LanguageId = -1;
}
}
Notice that
the class is declared as a partial class. The other half of the partial
class is generated by the Object Relational Designer. You can find the
Designer-generated half of the CodeSample partial class in the
Superexpert.Designer.cs file in the App_Code folder.
The
preceding partial class contains a method named SelectByEntryId. This
method executes a LINQ to SQL query that returns all code samples
associated with a certain entryId. The Table property used within the
method is a property exposed by the base. Notice that the class also
includes an OnCreated() method. Unfortunately, you can’t add a
constructor to a LINQ to SQL partial class because the Object Relational
Designer already creates a constructor. However, you can create the
equivalent of a class constructor by handling the OnCreated() event. In
the case of the CodeSample class, the OnCreated() event is used to
provide a default value for the CodeSample.LanguageId property.
When
building the LINQ to SQL queries for the sample application, I noticed
that I ended up writing almost the exact same queries for each entity.
For example, I needed to write Select, Insert, Update, and Delete
queries for both the Blog and the CodeSample entities. Whenever you find
yourself writing duplicate code, you should stop yourself and determine
whether there is a way to make the code more generic. In this case, I
took advantage of a custom entity base class.
All the
entity partial classes derive from the base EntityBase class. This class
contains generic Get, Select, Update, Delete, and Insert methods. It
also contains generic methods for sorting and paging database data. For
example, the Blog partial class is declared like this: public partial
class Blog :
EntityBase<Blog>
Because the
Blog class derives from the EntityBase class, it includes methods such
as Get, Select, Insert, Update, and Delete for free. It inherits all the
properties and methods of the EntityBase class (the EntityBase class is
located in the App_Code\EntityBaseClasses folder). The sample
application includes forms for inserting and updating blog entries,
inserting and updating code sample entries, and inserting and updating
individual code samples. All the forms in the sample application follow
the same pattern. A FormView that contains a single EditItemTemplate is
used for displaying the form for both inserting and
updating the form data.
For example, the page used for inserting and updating a blog entry is contained in Listing 2.
LISTING 2 Edit.aspx
<%@ Page Language=”C#” MasterPageFile=”~/Design/MasterPage.master” Title=”Blog Post” %>
<script runat=”server”>
/// <summary>
/// Add default values for new blog entry
/// </summary>
protected void srcBlog_Updating
(
object sender,
ObjectDataSourceMethodEventArgs e
)
{
// If new blog entry, add user name
Blog newBlog = (Blog)e.InputParameters[1];
if (newBlog.Id == 0)
{
newBlog.AuthorUserName = User.Identity.Name;
}
}
/// <summary>
/// If no problems, then redirect to blog tags page
/// </summary>
protected void srcBlog_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception == null)
{
Blog newBlog = (Blog)e.ReturnValue;
Response.Redirect(“~/Admin/BlogTags/Edit.aspx?blogId=” + newBlog.Id);
}
}
/// <summary>
/// If there was a problem, keep the form in edit mode
/// and show validation errors
/// </summary>
protected void frmBlog_ItemUpdated(object sender, FormViewUpdatedEventArgs e)
{
if (e.Exception != null)
{
e.KeepInEditMode = true;
Data Access and Validation
e.ExceptionHandled = true;
ValidationUtility.ShowValidationErrors(this, e.Exception);
}
}
</script>
<asp:Content ID=”Content1” ContentPlaceHolderID=”cphMain” Runat=”Server”>
<asp:UpdatePanel ID=”up1” runat=”server”>
<ContentTemplate>
<asp:FormView id=”frmBlog” DataSourceID=”srcBlog” DataKeyNames=”Id,Version”
DefaultMode=”Edit” OnItemUpdated=”frmBlog_ItemUpdated” Width=”100%” Runat=”server”>
<EditItemTemplate>
<div class=”field”>
<div class=”fieldLabel”>
<asp:Label id=”lblTitle” Text=”Title:” AssociatedControlID=”txtTitle” Runat=”server” />
</div>
<div class=”fieldValue”>
<asp:TextBox id=”txtTitle” Text=’<%# Bind(“Title”) %>’ Columns=”60” Runat=”server” />
</div>
<div class=”fieldValue”>
<super:EntityCallOutValidator id=”valTitle” PropertyName=”Title” Runat=”server” />
</div>
</div>
<div class=”field”>
<div class=”fieldLabel”>
<asp:Labelid=”lblIntroductionText” Text=”Introduction Text:”
AssociatedControlID=”txtIntroductionText” Runat=”server” />
</div>
<div class=”fieldValue”>
<asp:TextBox id=”txtIntroductionText” Text=’<%# Bind(“IntroductionText”) %>’
TextMode=”MultiLine” Columns=”60” Rows=”4” Runat=”server” />
</div>
<div class=”fieldValue”>
<super:EntityCallOutValidator id=”valIntroductionText”
PropertyName=”IntroductionText” Runat=”server” />
</div>
</div>
<div class=”field”>
<div class=”fieldLabel”>
<asp:Label id=”lblPost” Text=”Post:” AssociatedControlID=”txtPost” Runat=”server” />
</div>
<div class=”fieldValue”>
<fck:FCKeditor id=”txtPost” BasePath=”~/FCKEditor/” ToolbarSet=”Superexpert”
Value=’<%# Bind(“Post”) %>’ Width=”600px” Height=”600px” runat=”server” />
</div>
<div class=”fieldValue”>
<super:EntityCallOutValidator id=”EntityCallOutValidator1” PropertyName=”Post”
Runat=”server” />
</div>
</div>
<div class=”field”>
<div class=”fieldLabel”>
</div>
<div>
<asp:CheckBox id=”chkIsPinned” Text=”Is Pinned” Checked=’<%# Bind(“IsPinned”) %>’
Runat=”server” />
</div>
</div>
<div class=”field”>
<div class=”fieldLabel”>
</div>
<div>
<asp:Button id=”btnNext” CommandName=”Update” Text=”Next” Runat=”server” />
</div>
</div>
</EditItemTemplate>
</asp:FormView>
</ContentTemplate>
</asp:UpdatePanel>
<super:EntityDataSource id=”srcBlog” TypeName=”Blog” SelectMethod=”Get”
UpdateMethod=”Save” OnUpdating=”srcBlog_Updating” OnUpdated=”srcBlog_Updated”
Runat=”Server”>
<SelectParameters>
<asp:QueryStringParameter Name=”id” QueryStringField=”blogId” />
</SelectParameters>
</super:EntityDataSource>
</asp:Content>
The page in
Listing 2 contains a FormView control bound to a DataSource control.
The FormView control contains a single EditItemTemplate template. Notice
that it does not include an InsertItemTemplate, even though the
FormView is used both for inserting new blog entries and editing
existing blog entries. By using a single template, you reduce the amount
of code you must write and maintain by half.
The
DataSource control includes a select QueryStringParameter that grabs an
id value from the query string. The DataSource control calls the Get()
method to grab an instance of the Blog entity when the page is first
requested. The Get() method is a method of the EntityBase class. It
looks like this:
public static T Get(int? id)
{
if (id == null)
return new T();
return Table.Single(GetDynamicGet(id.Value));
}
When a null
ID is passed to the Get() method, it simply returns a new instance of
the entity (in this case, the Blog entity). If the id parameter does
have a value, on the other hand, the entity with a matching ID is
retrieved from the database with the help of the GetDynamicGet() method.
Therefore, if you request the page in Listing 2 without passing an id
parameter in the query string, a form that represents a new Blog entry
is displayed. Otherwise, if you do pass an id parameter, a form for
editing the existing Blog entity is displayed.
Handling Form Validation
The
sample application discussed in this chapter does not use any of the
standard ASP.NET validation controls. Validation is handled at the
entity level. In other words, validation is performed in the business
logic layer, where validation should be performed, instead of the user
interface layer.
The
EntityBase class includes an abstract (MustInherit) method named
Validate(). Each of the entities implements this abstract method. All
the validation logic is contained in the entity’s Validation() method.
For example, Listing 3 contains the Validation() method used by the Blog
entity.
LISTING 3 Blog.cs (Partial)
public partial class Blog : EntityBase<Blog>
{
/// <summary>
/// Where all validation happens
/// </summary>
protected override void Validate()
{
// Required fields
if (!ValidationUtility.SatisfiesRequired(Title))
ValidationErrors.Add(“Title”, “Required”);
if (!ValidationUtility.SatisfiesRequired(IntroductionText))
ValidationErrors.Add(“IntroductionText”, “Required”);
if (!ValidationUtility.SatisfiesRequired(Post))
ValidationErrors.Add(“Post”, “Required”);
}
}
The
Validate() method takes advantage of the ValidationUtility to check for
several required fields. If any of the validation checks fails, an error
message is added to the entity’s ValidationErrors collection. If you
look closely at the EditItemTemplate contained in the FormView in
Listing 2, you’ll notice that EntityCallOutValidator controls are
associated with each TextBox. For example, the following
EntityCallOutValidator control is associated with the txtTitle TextBox:
<super:EntityCallOutValidator id=”valTitle” PropertyName=”Title” Runat=”server” />
When the
ValidationUtility.ShowValidationErrors() method is called in the
frmBlog_ItemUpdated() event handler, any validation error that matches
the value of an EntityCallOutValidator control’s PropertyName property
is displayed. The advantage of placing your validation logic in your
business logic layer is that your validation logic is applied
automatically wherever you use the entity. For example, if the Blog
entity is used in multiple pages (or even multiple applications), you
don’t need to rewrite the very same validation logic.
The
traditional advantage of placing your validation logic in the user
interface layer is responsiveness. You don’t need to perform a postback
to view validation error messages. However, Ajax is blurring this
traditional divide between server and client. All the forms used in the
sample application use the UpdatePanel control in order to make the
forms more responsive.
Taking Advantage of Ajax
The
sample application discussed in this chapter takes advantage of the
Microsoft serverside AJAX controls. The UpdatePanel control is used with
almost all the forms for inserting and editing data. The sample
application also takes advantage of the ASP.NET AJAX Control Toolkit.
Two controls from the Toolkit, the AutoCompleteExtender and the Rating
control, are used to create a more interactive experience.
Using the UpdatePanel Control
Almost
all the FormView controls used in the sample application are wrapped in
an UpdatePanel control. When you submit a form, a disruptive postback
is not performed. Instead, a sneaky postback is performed in the
background by the UpdatePanel control. The overall user experience is
improved by the UpdatePanel control. For example, the UpdatePanel
control creates the illusion that the validation error messages are
being generated on the client when, in fact, the validation error
messages are being generated by the server.
However,
using the UpdatePanel control made debugging the sample application more
difficult. The UpdatePanel control prevents normal error messages from
being displayed in the browser. Furthermore, because an UpdatePanel
control times out, stepping through code with the Visual Web Developer
debugger in a page that contains an UpdatePanel is difficult.
Using the ASP.NET AJAX Control Toolkit
The sample application uses two controls from the ASP.NET AJAX Control Toolkit:
the AutoCompleteExtender control and the Rating control.
The
AutoCompleteExtender control can be used to extend a TextBox control so
that suggestions appear while you type (like in Google Suggest). The
suggestions are retrieved from a web method. The web method can be
defined in the page that contains the AutoCompleteExtender control, or
the web method can be defined in a separate web service. The
AutoCompleteExtender control is used in multiple pages within the sample
application. For example, it is used both in the page for editing blog
tags (see Figure 11) and in the page for editing code sample tags.
Listing 4 is extracted from the page for editing blog tags.
LISTING 4 Admin\BlogTags\Edit.aspx (Partial)
<asp:TextBox id=”txtTag” AutoComplete=”Off” Text=’<%# Bind(“Name”) %>’ Runat=”server” />
<ajaxToolkit:AutoCompleteExtender ID=”AutoCompleteExtender1” ServiceMethod=”GetSuggestions”
TargetControlID=”txtTag” MinimumPrefixLength=”1” Runat=”server” />
Figure11:
Notice that
the TextBox control in Listing 4 includes an AutoComplete=”Off”
attribute. This attribute disables the built-in browser auto-complete
(for Internet Explorer and Firefox) so that it does not interfere with
the Ajax auto-complete. In Listing 4, the AutoCompleteExtender is
associated with the TextBox control through its TargetControlID
property. The MinimumPrefixLength property configures the control to
start displaying suggestions as soon as you type at least one character
into the TextBox control. Finally, the AutoCompleteExtender control is
set up to retrieve its suggestions from a web method named
GetSuggestions(). This method is declared in the same page as the
AutoCompleteExtender control. The code for the GetSuggestions() method
is contained in Listing 5.
LISTING 5 Admin\BlogTags\Edit.aspx (Partial)
[System.Web.Services.WebMethod]
public static string[] GetSuggestions(string prefixText, int count)
{
return BlogTag.GetSuggestions(prefixText, count);
}
When a web
method is declared in a page, it must be declared as a static method.
Furthermore, it must be decorated with the WebMethod attribute. The
GetSuggestions() method in Listing 5 calls the GetSuggestions() method
of the BlogTag entity to get existing blog tags that match the prefix
from the database. The BlogTag.GetSuggestions() method is contained in
Listing 6.
LISTING 6 BlogTag.cs (Partial)
public partial class BlogTag : EntityBase<BlogTag>
{
public static string[] GetSuggestions(string prefixText, int count)
{
return Table.Where( t => t.Name.StartsWith(prefixText) )
.Select(t => t.Name).Distinct().Take(count).ToArray();
}
}
The other
control from the ASP.NET AJAX Control Toolkit used in the sample
application is the Rating control. This control is used to enable users
to rate the quality of a code sample (see Figure 12). The Rating control
is declared with the following attributes in the CodeSamples\Entry.aspx
page:
<ajaxToolkit:Rating ID=”Rating1” BehaviorID=”RatingBehavior1” CurrentRating=”2” MaxRating=”5”
StarCssClass=”ratingStar” WaitingStarCssClass=”savedRatingStar” FilledStarCssClass=”filledRatingStar”
EmptyStarCssClass=”emptyRatingStar” runat=”server” style=”float:left” Tag=’<%# Eval(“Id”) %>’
OnChanged=”Rating1_Changed” />
When a user
clicks the Rating control and selects a rating, the Rating control
raises its Changed event. The Changed event is handled by the following
event handler:
protected void Rating1_Changed(object sender, RatingEventArgs e)
{
EntryRating.Insert( new EntryRating(){EntryId=Int32.Parse(e.Tag), Rating = Int32.Parse(e.Value)} );
}
Figure 12:
This event
handler inserts a new EntryRating entity into the database that
represents the user rating. The second parameter passed to this method
is an instance of the RatingEventArgs class. Two properties of this
class are used when inserting the new record: Value and Tag. The Value
property represents the rating the user selected. The Tag property can
represent any information you want to associate with the rating. When
the Rating control is declared, the code sample Entry.Id is assigned to
the tag.The Rating control performs a normal server postback when you
select a particular rating.
Using the VirtualPathProvider Class
The
VirtualPathProvider class was introduced into the ASP.NET 2.0
Framework. Not many people know about this class. The
VirtualPathProvider class can be used to abstract the location of an
ASP.NET file away from the file system. I want visitors to the code
sample website to be able to try the code samples “live.” Someone
browsing the code samples should be able to click a Try It link and
execute a code sample (see Figure 13).
In the
sample application, code samples are stored in a database table. In
order to get the Try It functionality to work, the application must be
able to execute code samples directly from the database. This is exactly
what the VirtualPathProvider class enables you to do. The code sample
application takes advantage of the VirtualPathProvider class to execute
ASP.NET pages directly from a database table. The VirtualPathProvider
class hijacks a particular file system path. Any path that starts with a
directory named /virtual is passed to VirtualPathProvider. For example,
when you click the Try It link next to a code sample, a request for a
page at the following location is sent to the server:
/virtual/codesample/166/ShowAjaxValidator.aspx
Because
this path starts with a directory named /virtual, the request gets
routed to the VirtualPathProvider class, which grabs the file from the
TryItCode column in the CodeSample database table. The file gets
dynamically compiled by the ASP.NET Framework and served as a normal
ASP.NET page.












