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).