65.9K
CodeProject 正在变化。 阅读更多。
Home

图片验证器 - ASP.NET 自定义控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (24投票s)

2007年6月17日

CPOL

4分钟阅读

viewsIcon

85232

downloadIcon

1077

一篇关于如何创建自定义控件以渲染包含随机文本内容的动态图像的文章,该图像可用于代码验证。

Screenshot - main.jpg

引言

本文介绍如何创建 ASP.NET 自定义控件来实现图片验证功能。什么是图片验证?您可能在很多网站上都见过这种实现,例如 microsoft.com、google.com、yahoo.com 等,在注册过程中。它仅仅用于验证用户的输入,通过将用户输入的验证码与表单中显示的随机验证码图像进行比对。这有助于应用程序识别自动提交的表单,并拒绝可能通过输入大量数据来导致网站崩溃的请求。

背景

我看到各种网站上都实现了这项功能,并在创建个人网站时想自己实现一下。当我在我的网站上着手实现这项功能时,遇到了一些问题。其中一些问题包括生成随机文本、持久化文本以便在回发期间与用户输入的验证码进行比对、避免将生成的验证码存储在视图状态或 URL 中等等。渐渐地,我找到了解决这些问题的方法。我将在下面的章节中解释这些解决方案。

要点

在这篇文章中很难解释示例应用程序中使用的每一段代码。因此,我将解释开发此自定义控件所涉及的关键步骤。

  1. 创建一个能够渲染 <img> 标签的自定义控件。
  2. 生成随机文本、持久化文本并将其渲染为图像。
  3. 将用户输入与持久化的随机文本进行比对。

创建一个自定义控件来渲染 <Img> 标签

首先,我们需要创建一个可以渲染标准的 HTML <img> 标签的自定义控件。它还应该生成一个动态 URL 并将其附加到生成的 <img> 标签的 src 属性中。

在示例中,通过从 WebControl 派生一个名为 ImageVerifier 的类来实现这一点,如下所示。

namespace NatsNet.Web.UI.Controls 
{ 
    [DefaultProperty("Text")] [ToolboxData(
       "<{0}:ImageVerifier runat="server">")]  

    public class ImageVerifier : WebControl, IHttpHandler 
    { 
        private string m_UniqueID = string.Empty; 
        public ImageVerifier(): base(HtmlTextWriterTag.Img) { } 
        private string MyUniqueID  { ... } 
        public string Text { ... }         
        private string GetRandomText() { ... } 
        
        protected override void OnInit(EventArgs e) { ... }         
        protected override void LoadControlState(object savedState) { ... }         
        protected override object SaveControlState() { ... }         
        protected override void Render(HtmlTextWriter output) { ... } 

        public void ProcessRequest(HtmlTextContext context) { ... }         
        public bool IsReusable { ... } 
    } 
}

除了从 WebControl 派生控件外,我还实现了 IHttpHandler。这是为了让控件在渲染正常的 <img> 标签的同时,还能自行渲染图像。

控件的渲染发生在 Render 方法内部。

protected override void Render(HtmlTextWriter output)
{
    output.AddAttribute(HtmlTextWriterAttribute.Src, 
                "ImageVerifier.axd?uid=" + this.MyUniqueID);
    base.Render(output);
    output.Write("<script language="'javascript'">");
    output.Write("function RefreshImageVerifier(id,srcname)");
    output.Write("{ var elm = document.getElementById(id);");
    output.Write("  var dt = new Date();");
    output.Write("  elm.src=srcname + '&ts=' + dt;");
    output.Write("  return false;");
    output.Write("}</script>");
    output.Write(" <a href='#' onclick=\"return RefreshImageVerifier('"
        + this.ClientID + "','ImageVerifier.axd?&uid="
        + this.MyUniqueID + "');\">Refresh</a>");
}

Render 方法中的第一行代码为要渲染的 <img> 标签分配 src 属性。它生成的 URL 格式为 "ImageVerifier.axd?uid=" + this.MyUniqueID。该控件有一个 MyUniqueID 属性,它将是为每个控件实例生成的唯一 GUID。Render 方法的最后一行输出一个链接,用于在不进行回发的情况下刷新图像。

生成随机文本、持久化文本并将其渲染为图像

为了将动态文本渲染为该控件的图像,我们将通过实现 IHttpHandler 方法 public void ProcessRequest(HttpContext context) 来使用同一个类 ImageVerifier。当浏览器请求由 <img> 标签的 src 属性指定的 URL 时,将调用此方法。为了实现这一点,我们需要在消耗应用程序的 web.config 文件中将我们的 ImageVerifier 类配置为 ImageVerifier.axd 等 URL 的 HTTPHandler。

<httpHandlers>
    <add
        verb="GET"
        path="ImageVerifier.axd"
        type="NatsNet.Web.UI.Controls.ImageVerifier, NatsNet.Web.UI.Controls"
        />
</httpHandlers>

下面是 ProcessRequest() 方法的实现

public void ProcessRequest(HttpContext context)
{
    Bitmap bmp = new Bitmap(180, 40);
    Graphics g = Graphics.FromImage(bmp);
    string randString = GetRandomText();
    string myUID = context.Request["uid"].ToString();

    if (context.Cache[myUID] == null)
        context.Cache.Add(  myUID, 
                            randString,
                            null, 
                            Cache.NoAbsoluteExpiration,
                            TimeSpan.FromMinutes(5), 
                            System.Web.Caching.CacheItemPriority.Normal, 
                            null
                        );
    else
        context.Cache[myUID] = randString;
        
    g.FillRectangle(Brushes.WhiteSmoke,0, 0, 180, 40);    
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;
    g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
    
    Random rand = new Random();    
    for (int i = 0; i < randString.Length; i++)
    {
        Font drawFont = new Font("Arial", 18,
            FontStyle.Italic | (rand.Next() % 2 == 0 ? 
            FontStyle.Bold : FontStyle.Regular));
        g.DrawString(randString.Substring(i,1), drawFont, 
            Brushes.Black, i * 35 + 10, rand.Next()% 12);

        Point[] pt = new Point[15];
        for (inti = 0; i < 15; i++)
        {
            pt[i] = newPoint(rand.Next() % 180, rand.Next() % 35);
            g.DrawEllipse(Pens.LightSteelBlue,pt[i].X, pt[i].Y, 
                          rand.Next() % 30 + 1, rand.Next() % 30 + 1);
        }
        
        context.Response.Clear();
        context.Response.ClearHeaders();
        context.Response.ContentType = "image/jpeg";
        bmp.Save(context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);        
        context.Response.End();
    }
}

ProcessRequest() 方法中,使用私有方法 GetRandomText() 生成随机文本,并使用 Cache 对象将其持久化,如上所示。查询字符串 uid 的值将用作在 Cache 对象中持久化随机文本的键。

为了生成特定长度的随机文本,我们可以编写自定义逻辑。这里在示例中,我使用了 Random 类来生成随机数并将其转换为相应的字符。您可以根据需要以不同的方式实现相同的逻辑。

private string GetRandomText()
{
    string uniqueID = Guid.NewGuid().ToString();
    string randString = "";
    
    for (int i = 0, j = 0; i < uniqueID.Length && j < 5; i++)
    {
        char l_ch = uniqueID.ToCharArray()[i];
        if ((l_ch >= 'A' && l_ch <= 'Z') || (l_ch >= 'a' && 
                l_ch <= 'z') || (l_ch >=  '0' && l_ch <= '9'))
        {
            randString += l_ch;
            j++;
        }
    }
    
    return randString;
}

将用户输入与持久化的随机文本进行比对

最后,我们需要将用户输入与显示为图像的随机文本进行验证。为此,我为我们的用户控件添加了一个 Text 属性。此属性检索存储在 Cache 中的随机文本并返回它。我们可以使用此值来验证用户输入。

public string Text
{
    get
    {
        return string.Format("{0}",
        HttpContext.Current.Cache[this.MyUniqueID]);
    }
}

下面的代码显示了在消耗应用程序中用于验证用户输入的代码示例。

protected void btnSubmit_Click(object sender, EventArgs e)
{
    if (txtImgVerifyText.Text == ImageVerifier1.Text)
    {
        // User has input the correct text so can continue
        // processing the request.
    }
    else
    {
        // Either user has input an incorrect value or the request
        // is not generated by the manual input. So do not continue
        // processing the request.
    }
}

关注点

在本文中,我解释了如何使用图像验证的概念来帮助应用程序识别自动提交的表单。此图像验证的实现使用了 HTTPHandlers、Random 类、Cache 对象等一些编码概念。

© . All rights reserved.