AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1698&pId=-1
Developing an ASP.NET AJAX Server Centric Based Mini Blog System - Part 3
page
by Xianzhong Zhu
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 40924/ 46

Introduction

Continuing from the last installment of this series, we will work with the blog system mainly from the point of view of common user management. Moreover, in the rest of this part we will summarize the key techniques required to construct this sample blog system. To gain a better understanding with this part let us again present to you the common user related modules to be discussed.

Figure 1 - The common users related modules

When the user clicks "Back to Home" at the login page, or at the first time he or she is navigated to the home page of this blog system, the user enters into the system as the role of a common user. At this time the user owns the right of viewing the blog list, reading the recommended blogs and passengers' comments and words, making comments or even leaving words to the blog host, all of which are previewed in Figure 1 in the first installment of this series.

You, in this case the unregistered user (a common surfer), now come to the homepage index.aspx and will be shown around the mini blog system. Naturally, the first view catching your sight may be the blog category and you can even skim through some blog articles and make related comments upon them.

As you know, both of the above actions are concerning the homepage index.aspx. Let us examine the first case.

Looking through the Blog Category

The blog category area is shown at the upper left corner of page index.aspx. Figure 2 illustrates the related scrap of snapshot.

Figure 2 - The blog category block at the homepage

This part relates to the following HTML code.

Listing 1

<asp:DataList id="ClassList" runat="server">
      <HeaderTemplate>
            <a href="Index.aspx">Home</a>
      </HeaderTemplate>
      <ItemTemplate>
            <a href='index_<%#Eval("ST_c_name")%>_<%#Eval("ST_c_id") %>.aspx '>
                  <%#Eval("ST_c_name")%>
            </a>
      </ItemTemplate>
</asp:DataList>

Here, the frequently-used ASP.NET 1.X/2.0 server control DataList comes into use. Partly due to that there are a great deal of sub items that will be dropped here and partly due to that each of the items will be linked to an array of blog articles to be shown at the right. I have tried to resort to the url redirection technique to get over this puzzle. As you have seen, the above complex href will be resolved into a piece of url scrap. With the url redirection defined inside the file web.config, when the left sub blog item is clicked then the user will be clicking a url scrap (a hyperlink) which will show him or her the related articles belonging to this category. For details about the url redirection, please refer to the "Key Techniques Dissection" section later.

You may think that I have followed an awkward routine. If so, you can solve it on you own.

To continue the story we look at reading interesting blogs and making related comments.

Reading Blogs and Making Comments

As you may have noticed, when clicking the items under "Recommended" or directly clicking one of the links [Click and Read] at the right, you will be navigated to another page, Show.aspx, where you can bat around the blog article and leave your valuable comments upon this article. Figure 3 gives you the related adapted snapshot when the user clicks the blog title "On ASP.NET AJAX."

Figure 3 - The user is navigated to the page to read the blog

At the middle of the right part there are maybe numerous comments already made by other readers. At the lower right lies a "sub form" for you to enter your comment.

As you have guessed from Figure 3, there are two points deserved to be discussed. The first one is the "Comments" area which corresponds to a DataList control enclosed by an ASP.NET AJAX server control UpdatePanel. In this way, when a new comment is submitted below only the "Comments" area will be updated. Listing 2 shows the HTML code with this area.

Listing 2

<asp:UpdatePanel runat="server" ID="upleft" UpdateMode="Conditional" >
      <ContentTemplate>
    <asp:DataList ID="DataListLeft" runat="server" 
        BackColor="LightGoldenrodYellow" 
        BorderColor="Tan" BorderWidth="1px" CellPadding="2" 
        DataSourceID="SqlDataSource1" Width="524px" ForeColor="Black">
        <FooterStyle BackColor="Tan" />
        <AlternatingItemStyle BackColor="PaleGoldenrod" />
        <SelectedItemStyle BackColor="DarkSlateBlue" ForeColor="GhostWhite" />
        <HeaderStyle BackColor="Tan" Font-Bold="True" />
        <ItemTemplate>
            <table>
                <tr>
                    <td>Nick Name
                    </td>
                    <td>Content
                    </td>
                </tr>
                <tr>
                    <td><asp:Label ID="ST_r_nickLabel" runat="server" 
                          Text='<%# Eval("ST_r_nick") %>' />
                    </td>
                    <td><asp:Label ID="ST_r_contentLabel" runat="server" 
                          Text='<%# Eval("ST_r_content") %>' />
                    </td>
                </tr>
            </table>
        </ItemTemplate>
         <SeparatorTemplate>
            <img alt="" src="images/Hr_book_pen.gif" />
        </SeparatorTemplate>
    </asp:DataList>
    <br />
    <asp:Label ID="lblMessage" runat="server" ForeColor="Red"></asp:Label>
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="replay" EventName="Click" />
    </Triggers>
    </asp:UpdatePanel>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
        SelectCommand=
"SELECT [ST_r_nick], [ST_r_content] FROM [ST_replay] Where ST_n_id=@ST_n_id"
        >
    <SelectParameters>
        <asp:QueryStringParameter  Name="ST_n_id" QueryStringField="id" 
          Type="Int32"/>
    </SelectParameters>
</asp:SqlDataSource>

Here the data of control DataList derives from table ST_replay which is performed using a typical two-tier schema. Moreover, you should take notice that the UpdatePanel control's trigger AsyncPostBackTrigger is bound to the click event handler of the "replay" button which is the "Submit" button below. This means that if "Submit" is clicked and related conditions are met then the UpdatePanel control surrounded area will be updated in the asychronous mode. Also, within the SelectParameters parameter of the SqlDataSource component, QueryStringParameter is used, which appropriately corresponds to the parameter data passed from the previous web page (in this case the homepage).

Next, let us shift our attention to the "Leave a Comment" area below.

With comment leaving there are also two points that need attention. The first one is the picture-styled verifying code, which is almost a must have in a modern website in order to abandon possible robot actions to improve the security of the system. For simplicity, we herein have not introduced the ASP.NET AJAX Toolkit control NoBot, which is designed to achieve the result from the standpoint of time limit. Only the data entered within the specified time is allowed; or else, it maybe suspected to be a robot action. For more details about the NoBot control, you can refer to the ASP.NET AJAX Toolkit control online tutorial.

With regard to the picture-styled verification, the basic idea is to draw some characters commingled with some pictures so that only users who enter the specified correct characters can continue to take the next step.

In this sample, to accomplish the above target we have recourse to the GDI+ technique and a custom HTTP handler GenerateCAPTCHA.ashx.  As you have guessed, all the secrets behind the scene are finished within the important function ProcessRequest defined in this HTTP handler. Listing 3 gives the complete source code of this function.

Listing 3 - The code for the custom HTTP handler GenerateCAPTCHA.ashx

// render the verfication code
public void ProcessRequest (HttpContext context) 
{
    // Create the Bitmap
    using (Bitmap objBitmap = new Bitmap(_width, _height, 
      PixelFormat.Format32bppArgb))
    {
        // create the canvas
        using (Graphics objGraphics = Graphics.FromImage(objBitmap))
        {
            //specify the smoothing mode 
            objGraphics.SmoothingMode = SmoothingMode.AntiAlias;
 
            //Define a rectangle to render the verfication code
            Rectangle rect = new Rectangle(0, 0, _width, _height);
            
            //define a brush with specified hatching style
            HatchBrush hBr = new HatchBrush(HatchStyle.WideDownwardDiagonal, 
                                              Color.Yellow, Color.White);
            // create a rectangle for renderiing picture
            objGraphics.FillRectangle(hBr, rect);
            hBr.Dispose();
            
            // define the character offset in the string
            int charOffset = 0;
            //compute the width for each character
            double charWidth = 
              Convert.ToDouble(_width) / Convert.ToDouble(_randomTextLength);
            //look on per character as a rectangle so as to twist it easily
            Rectangle rectChar;
            // define the  fond style
            Font fnt = null;
            
            //first use a black color brush to render the 
            //letter within the rectangle, then twist the 
            //rectangle, and at last use the rectangle to draw
            using (Brush br = new SolidBrush(Color.Black))
            {
                foreach (Char ch in _randomText)
                {
                    fnt = GetFontStyle();
                    rectChar = new Rectangle(Convert.ToInt32(charOffset * charWidth), 
                      0, Convert.ToInt32(charWidth), _height);
 
                    GraphicsPath gp = TextPath(ch.ToString(), fnt, rectChar);
                    TwistText(gp, rectChar);
                    objGraphics.FillPath(br, gp);
                    charOffset += 1;
                }
            }
 
            // add some background noise points as well as the line noise
            AddNoise(objGraphics, rect);
            AddLine(objGraphics, rect);
 
            // specify the ContentType of the Response object
            //, and require when the drawing is finished 
            // return the result to the browser side                
            context.Response.ContentType = "Image/Jpeg";
            context.Response.Clear();
            // set the Cache-Control: no-cache
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            context.Response.BufferOutput = true;
            context.Response.Flush();
            objBitmap.Save(context.Response.OutputStream, ImageFormat.Jpeg);
        }
            
        HttpApplication app = context.ApplicationInstance;
 
        //you can use the following two setence to create a unique 
        //GUID to access the newly-generated verfying code
        //here we simply hard code it
        //Guid guid = System.Guid.NewGuid();
        // string strGuid = guid.ToString();
        string strGuid = "CAPTCHA";
 
        if (strGuid != String.Empty)
        {
            HttpRuntime.Cache.Insert(strGuid, _randomText);
        }
    }    
}

Between the above lines we have supplemented enough comments so I trust you do not need additional explanations. As for the other helper functions used, you can refer to the source code downloadable at the end of this article.

Last but not the least, you are sure to remember the little table named codetable. As pointed out, it is defined to persist the verifying codes to help to generate the verifying picture. In this sample we put a string "GenerateCAPTCHATest" to the field Code in table codetable to be used to generate the verification code (five characters are selected at random here).

The second point worth noticing is the ModalPopupExtender entender, which is used in this case to simulate a desktop-styled modal dialog to gain better user experience. The related HTML code is shown in Listing 4.

Listing 4

<asp:Panel ID="popPanel1" runat="server" Height="233px" Width="341px" 
    CssClass="confirmPanel" BorderStyle="Inset" BackColor="#CCCCFF" 
    BorderColor="#CCCCFF" Style="display:none" >
    <div id="titleDiv" 
            style="background-color: #6666FF; color: #0000FF; font-weight: bold; 
font-size: 20px; background-image: url('images/formback.GIF'); 
background-repeat: repeat;">Warning</div>
    <div align='center' style="background-color: #008080; border-style: inset; 
border-color: #800000; height: 179px; text-align: left; font-size: 16px; 
color: #FF0000;" id="bodyDiv">
        Sorry, the nickname, title,content cannot be empty!<br />
        <br />
        Please try again.</div>
    <div style="text-align: center; clip: rect(5px, auto, auto, auto);">
    
    <asp:Button ID="btnOK" runat="server" Text="OK" CssClass="ButtonCss" 
         Width="66px" Height="26px"></asp:Button>
    </div>
</asp:Panel>
<cc1:ModalPopupExtender ID="ModalPopupExtender1" runat="server" Drag="True" 
    DropShadow="True" OkControlID="btnOK" PopupControlID="popPanel1" 
    TargetControlID="btnOK" PopupDragHandleControlID="popPanel1">
</cc1:ModalPopupExtender>

Here, we have introduced an ASP.NET Panel control with an "OK" button and an <div/> element in it. At the very beginning the Style property of the panel is set to "display:none" and the <div/> element is embroidered with a special .gif image. Lastly, the ModalPopupExtender is bound to the Panel control with each property assigned to the proper value.

At the running time, if the user clicks "Submit" to leave a comment while leaving some required fields empty, an AJAX-styled modal dialog will appear to warn the user. Figure 4 gives the simulated modal dialog in action when all the required fields are not populated with stuffs.

Figure 4 - Use ModalPopupExtender to simulate a desktop-styled modal dialog

Of course the dialog in Figure 4 is an AJAX-styled one.

Next, let us look at the "Submit" button related click event handler, as is shown in Listing 5.

Listing 5

protected void replay_Click(object sender, System.EventArgs e)
{
    Object httpCache = System.Web.HttpRuntime.Cache.Get("CAPTCHA");
    string httpCacheCAPTCHA = "";
 
    
    if (r_nick.Text.Trim() == "" || r_title.Text.ToString().Trim() == "" || 
        r_content.Value.Trim() == "")
    {
        ModalPopupExtender1.Show();
        lblMessage.Text = "";
    }
    else
    {
 
        if (httpCache != null)
            httpCacheCAPTCHA = httpCache.ToString();
 
        if (txtCAPTCHA.Text  == httpCacheCAPTCHA)
        {
            try
            {
                // Insert Item 
                string ST_sql = 
"insert into ST_replay" + 
"(ST_r_nick,ST_r_title,ST_r_content,ST_r_date,ST_n_id) values ('" + 
r_nick.Text.Trim() + "','" + r_title.Text.Trim() + "','" + 
r_content.Value.Trim() + "','"  + System.DateTime.Now + "'," + Request.QueryString["id"+ ")";
                SqlCommand ST_myCmd = new SqlCommand(ST_sql, ST_myConn);
                ST_myConn.Open();
                ST_myCmd.ExecuteNonQuery();
                ST_myConn.Close();
 
                DataListLeft.DataBind();
 
                ST_add_Re();
 
                r_nick.Text = "";
                r_title.Text = "";
                r_content.Value = "";
                txtCAPTCHA.Text = "";
            }
            catch (HttpException ex)
            {
                lblMessage.Text = "Submit error. Cause:br />" + ex.Message;
            }
        }
        else
        {
            lblMessage.Text = "The verifying code is incorrect, please try again!";
            txtCAPTCHA.Text = "";
        }
    }
}

If the three required fields are not populated with valid characters (some of which are empty), we invoke ModalPopupExtender1.Show() to prompt to the user; or else, by using an proper INSERT sql clause the newly-added record is sent to the backend database. And, at the same time, the related controls at the presentation tier are updated.

Let us take a look at a common block in many forum-styled websites: leaving a message to the blog host (or, in other scenarios, the website host).

Leaving a Message

When the common user clicks the link "Leave a Word" below the "About Me" page, Message.aspx is invoked. For a clearer understanding with the implementing logic take look at the design-time snapshot.

Figure 5 - The design-time snapshot to leave a message

First, you will notice that this page is a content page of the master page, OneColumn.master, since there is no category menu required.

The whole layout is composed of three parts: the topmost navigating bar, the left part for the user to leave new message, and the right to show all the already left words.

Shrewd readers may have noticed that the left part is pretty similar to the lower right part of page Show.aspx. Surely it is. But you should also notice that we have painstakingly introduced the AJAX Control Toolkit extender NoBot.

NoBot is one of the great ASP.NET AJAX Toolkit controls that attempts to provide CAPTCHA-like bot/spam prevention without requiring any user interaction. This approach is easier to bypass than an implementation that requires actual human intervention, but NoBot has the benefit of being completely invisible.  NoBot is probably most relevant for low-traffic sites where blog/comment spam is a problem and 100% effectiveness is not required. For more details about the NoBot extender, please refer to the ASP.NET AJAX Control Toolkit online tutorials.

Author's Note: You can test NoBot by violating any of the above techniques: posting back quickly, posting back many times, or disabling JavaScript in the browser.

Now, let us look at the NoBot related HTML code.

Listing 6

<ajaxToolkit:NoBot ID="myNoBot" runat="server"
    ResponseMinimumDelaySeconds="15"
    CutoffMaximumInstances="3"
    CutoffWindowSeconds="150"
    OnGenerateChallengeAndResponse="MyChallengeResponse" />

The two properties, ResponseMinimumDelaySeconds and OnGenerateChallengeAndResponse of NoBot, need to be discussed. First, property ResponseMinimumDelaySeconds is set to 15 seconds, which means that within this period if the "Submit" button is pressed then the current action is suspected to be some robot one, as a result of which the submitting content is rejected and a warning message is thrown out at the left bottom of the page.

Next, take a look at the property OnGenerateChallengeAndResponse related value- a javascript function, as is shown in Listing 7.

Listing 7 - The NoBot's OnGenerateChallengeAndResponse property related javascript function

protected void MyChallengeResponse(object sender, NoBotEventArgs e)
{
    Panel p = new Panel();
    Random rand = new Random();
 
    // set the related properties for the panel
    p.ID = "panCheckNoBot";
    p.Width = rand.Next(123);
    p.Height = rand.Next(456);
    // hide Panel
    p.Style.Add(HtmlTextWriterStyle.Visibility, "hidden");
    p.Style.Add(HtmlTextWriterStyle.Position, "absolute");
    // add control Panel into the  NoBot control
    ((NoBot)sender).Controls.Add(p);
    // specify the chanllenge and response
    e.ChallengeScript = String.Format(
        "var e = document.getElementById('{0}'); e.offsetWidth + e.offsetHeight;",
        p.ClientID);
    e.RequiredResponse = (p.Width.Value + p.Height.Value).ToString();
}

In this function, a <div> element with random width and height is generated and then added onto the DOM tree in the page. At the same time, the sum of the width and height is stored, and a javascript code snippet is written in the page. Note that this javascript code snippet will be invoked at the client-side running time to find the above <div> and obtain the factual sum of its width and height. In this way when the page postback takes place, the NoBot control can compare the expected value with that fetched from the browser to judge whether the client side is a browser and further judge whether it is a robot action or not.

The second point requires is the right DataList control. As you have guessed, it is enclosed with an UpdatePanel control. If the left new record is inserted into the database table, the DataList control will accordingly be updated in the AJAX way. Since the implementation is easy to follow, we are won't go into more detail.

As of now, we have examined all the web pages in the blog sample website. Next, we are going to summarize the key techniques used in writing the sample application.

Key Techniques Summarization

Before delving into the construction of the system, let us enumerate the key techniques leveraged in this blog system.

·         A simple two-tier architecture

·         ASP.NET 2.0/3.5 components, such as GridView, FormView, DataList, SQLDataSource, Master Page, SiteMapPath

·         The main ASP.NET AJAX server-side controls, such as ScriptManager, UpdatePanel

·         ASP.NET AJAX Control Toolkit controls, such as CalendarExtender, ModalPopupExtender, NoBot, PasswordStrength, PopupControlExtender, TextboxWatermarkExtender, ToggleButtonExtender

·         URL rewrite technique

Altogether, during the course of authoring the mini blog sample website, I tried to leverage ASP.NET 2.0 controls. As for the AJAX technique, it is probably well-known that the ASP.NET AJAX server-centric programming is mainly centering on the ScriptManager and UpdatePanel controls. ScriptManager serves as the control center of the client side while UpdatePanel is used to achieve the partial update of a web page in the asynchronous way. What is more, as for ASP.NET AJAX Control Toolkit, it is a set of ASP.NET server components and controls which help to extend the functionalities of the original ASP.NET server counterparts. Joyfully, most of the extenders are pretty easy to useeven with no coding. On the other hand, due to the features of our sample applications, only a small number of the extenders were put into use.

Concerning the URL rewriting (also redirection) technique, it is a long story. So I will only say a few words about it.

URL rewriting is a most powerful technique available to control the URL presented to the user, as well as to maintain websites' permalinks. It is very important to most website content owners that their links remain permanent, hence "permalink." As a protocol, HTTP provides two ways to alert the browser: the first is a temporary redirect, or 302, and the second is a permanent redirect, or 301.

In ASP.NET v2.0, a new URL mapping technique comes into being to simplify the URL rewriting programming by introducing a new urlMappings configuration element into file web.config. Therefore, what you need to do is add a new entry to web.config to achieve the aim of redirecting a url. The following gives a simple sample for mapping a URL.

Listing 8

<urlMappings enabled="true">
  <add url="~/Articles/AspDotNet/UrlRewriting"
  mappedUrl="~/Articles.aspx?cat=1&id=16" />
</urlMappings>

The url attribute is what the user sees and the mappedUrl attribute describes the actual requested page. In the preceding urlMappings element, imagine that there is an articles page that dynamically returns articles based on category and article identifier. The url shows the preferred user interface, but the mappedUrl attribute shows the actual page and parameters that will be requested.

Regrettably, you cannot use regular expressions in the urlMappings configuration element introduced by ASP.NET 2.0.

In this sample, we used URL rewriting to organize required information into a logical hierarchy or to mask query string parameters, all of which were achieved by a great tool URLRewriter.dll written by Scott Mitchell. You can download this tool from here.

For detailed implementation, please refer to the contents in file web.config and I highly recommend you also refer to Scott Mitchell's article on MSDN.

Downloads
References
Conclusion

In this installment we have accomplished the common user related functionalities and tasks. And later, we enumerated the main techniques utilized in this blog demo application. As we have seen in this demo, by using ASP.NET AJAX supplied techniques we have not only enhanced the user experience, but also greatly improved the system efficiency - with AJAX styled asynchronous page update substituting for the traditional whole page post back.

On the other hand, note that, for simplicity, we assigned the same rights to both the administrator and the blog hosts. However, in practical situations, this is not really the case - the administrator often owns more control rights than the blog hosts. And also, for simplicity, we have only used a two-tier architecture to build up the blog sample website while in real scenarios a typical three-tier architecture is recommended to gain better control over the system and maintenance and extensibility. Moreover, allowing for merely a demo application, we have selected the built-in mode in terms of the backend database. We directly put the demo database under the website's sub folder App_Data. However, in practical situations, you are suggested to use the outside independent SQL Server database to serve as the backend database, which requires different configurations with those used in this demo. In terms of utilizing ASP.NET AJAX server controls, ASP.NET AJAX Control Toolkit extenders and other AJAX related techniques, we did touch a few of them. The reason mainly lies in the two aspects: time limitation and the two-tier architecture.

Although I have built this blog demo under Visual Studio 2008, with only a few modifications with the ASP.NET AJAX topic you can also easily get it through in your possible Visual Studio 2005 environment.



©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-16 4:07:51 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search