Building a Code Sample Website


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 3:

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 9:

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.

2 comments:

  1. Hi,Dynamic Web Design Cochin, enabling you to design, develop, and maintain extraordinary standards-based Web sites. This makes the design of a ecommerce website even easier.Thanks.......

    ReplyDelete
  2. Microsoft ASP.NET Course in Delhi- RKM IT institute providing asp.net training, asp.net classes, asp.net course and live project in Delhi. Microsoft's ASP.net is a server-side scripting technology. Net is a platform to develop dynamic web pages in an easy way. .NET is one of the popular framework for developing websites and software.

    ReplyDelete