2010年6月4日 星期五

圖形驗證(4): 使用 Generic Handler 產生圖形

這個系列,請見
(1) 簡介
(2): ASP.NET 簡易實作圖形驗證
(3): ASP.NET 圖形加強版

上一篇加強了圖形後,發現了一個美中不足的部份。圖形是先產了圖檔後,再以 asp:image 元件指到該圖檔,讓使用者可以看圖猜字。但這樣長久下來,會讓圖檔愈來愈多,總有一天會讓硬碟爆掉。雖然可以定下MIS規則讓管理人員定期手動清理,但人是健忘的動物,還是有一天會忘的。

這一次,我們就來加強一下這個部份。

Generic Handler

我們將使用 Generic Handler來解決暫存圖檔的問題。首先新增一個  Captcha.ashx 到網站中。

image

由於在 ashx 中,我們要使用 Session,故必須實作 IRequiresSessionState,如下

public class Captcha : IHttpHandler, IRequiresSessionState 
{

public void ProcessRequest(HttpContext context)
{
  context.Response.ContentType = "image/gif";
  VerifyNow(context);
}

public bool IsReusable
{
  get
  {
    return false;
  }
}

接下來,經過重構之後,之前大部份關於Captcha 圖形驗證的程式都可以搬到 Captcha.ashx.cs 下了。程式如下

Captcha.ashx.cs

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Web;
using System.Web.SessionState;

namespace Captcha.Utility
{
  /// <summary>
  /// Summary description for Captcha
  /// </summary>
  public class Captcha : IHttpHandler, IRequiresSessionState 
  {

    public void ProcessRequest(HttpContext context)
    {
      context.Response.ContentType = "image/gif";
      VerifyNow(context);
    }

    public bool IsReusable
    {
      get
      {
        return false;
      }
    }

    private void VerifyNow(HttpContext context)
    {
      string randomString = context.Session["ImgText"] as string;
      CreateImage(context, randomString);
    }

    

    private void CreateImage(HttpContext context, string imageText)
    {
      //建立一個 Bitmap。寬高未定,故先給定1, 1
      Bitmap bmpImage = new Bitmap(1, 1);

      //指定字型
      Font font = new Font("Verdana", 24, FontStyle.Bold, GraphicsUnit.Point);

      //進行繪圖。繪圖需透過 Grahpics 物件。
      Graphics graphics = Graphics.FromImage(bmpImage);
      //測量文字的寬高
      int width = (int)graphics.MeasureString(imageText, font).Width;
      int height = (int)graphics.MeasureString(imageText, font).Height;

      //重新指定 Bitmap
      bmpImage = new Bitmap(bmpImage, new Size(width, height));

      //重新繪圖
      Random r = new Random();
      graphics = Graphics.FromImage(bmpImage);
      graphics.Clear(r.NextColor()); //使用亂數底色
      graphics.TextRenderingHint = TextRenderingHint.AntiAlias;  //不要鋸齒

      int avgWidth = width / imageText.Length;
      for (int i = 0; i < imageText.Length; i++)
      {
        font = new Font("Verdana", r.Next(12, 24), FontStyle.Bold, GraphicsUnit.Point);
        graphics.DrawString(imageText.Substring(i, 1), font, new SolidBrush(r.NextColor()), avgWidth * i, 0); //把文字畫上去
      }

      graphics.Flush();

      for (int i = 0; i < 10; i++)
        DrawRandomLine(graphics, height, width, r);

      bmpImage.Save(context.Response.OutputStream, ImageFormat.Gif);

      //記得 release 記憶體
      graphics.Dispose();
      font.Dispose();
      bmpImage.Dispose();
    }

    private static Color NextColor(Random r)
    {
      return Color.FromArgb(r.Next(255), r.Next(255), r.Next(255));
    }

    private static void DrawRandomLine(Graphics graphics, int height, int width, Random random)
    {
      Pen pen = new Pen(random.NextColor());
      pen.Width = random.Next(3);
      Point p1 = new Point(random.Next(width), random.Next(height));
      Point p2 = new Point(random.Next(width), random.Next(height));
      graphics.DrawLine(pen, p1, p2);
    }
  }
}
網頁的部份,就只剩下取得密碼及驗證的部份
Sample1.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Sample1.aspx.cs" Inherits="Captcha.Sample1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <asp:Image runat="server" ID="image" ImageUrl="~/Utility/Captcha.ashx" />
      <asp:TextBox runat="server" ID="txtAnswer" />
      <asp:Button Text="輸入" runat="server" ID="btnSubmit" onclick="btnSubmit_Click" />
    </div>
    </form>
</body>
</html>

Sample1.aspx.cs

using System;
using System.Text;
using System.Web.UI;

namespace Captcha
{
  public partial class Sample1 : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e) 
    {
      if (!Page.IsPostBack)
      {
        CreateImageText();
      }
    }

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
      string randomString = Session["ImgText"] as string;
      if (randomString != txtAnswer.Text.Trim())
      {
        Response.Write("驗證錯誤");
        CreateImageText();
      }
      else
        Response.Write("驗證成功");
    }

    private void CreateImageText()
    {
      string randomString = GetRandomString(4);
      Session["ImgText"] = randomString;
    }

    private string GetRandomString(int length)
    {
      char[] chars = @"23456789ABCDEFGHIJKLMNPQRSTUVWXYZ".ToCharArray();
      Random r = new Random((int)DateTime.Now.Ticks);
      StringBuilder sb = new StringBuilder(length);
      for (int i = 0; i < length; i++)
        sb.Append(chars[r.Next(chars.Length)]);
      return sb.ToString();
    }
  }
}

結論

經過這樣一重構,ashx 的可重用性變高了,也不再需要圖片的暫存檔。

範例下載

沒有留言:

Share with Facebook