架構優勢
- 分離關注點:.ashx 專責圖片產生,.aspx 專責頁面邏輯
- 無 ViewState/頁面生命週期:輕量、回應快
- 易於快取控制:可精確設定
Response.Cache
C# 版 ValidationCode.ashx
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Web;
public class ValidationCode : IHttpHandler
{
static Random r = new Random(Guid.NewGuid().GetHashCode());
// 取得 Brushes/Pens 所有公開靜態屬性(排除黑色/透明)
static PropertyInfo[] colors = typeof(Brushes)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(p => p.Name != "Black" && p.Name != "Transparent").ToArray();
static PropertyInfo[] lineColors = typeof(Pens)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(p => p.Name != "Black").ToArray();
static object brushObj = typeof(Brushes).GetConstructor(BindingFlags.NonPublic, null, Type.EmptyTypes, null);
static object penObj = typeof(Pens).GetConstructor(BindingFlags.NonPublic, null, Type.EmptyTypes, null);
const float CharWidth = 40f;
const float CharHeight = 50f;
public void ProcessRequest(HttpContext context)
{
int count = 5;
int.TryParse(context.Request.QueryString["reqNum"], out count);
Bitmap bmp = new Bitmap((int)(CharWidth * count), (int)CharHeight);
Graphics g = Graphics.FromImage(bmp);
string code = "";
for (int i = 1; i <= count; i++)
{
code += r.Next(0, 9);
var prop = colors[r.Next(colors.Length)];
Brush brush = (Brush)prop.GetValue(brushObj, null);
g.DrawString(code[i-1].ToString(), new Font("黑體", CharWidth), brush, new PointF((i-1)*CharWidth, 0));
}
// 干擾線
int lineCount = r.Next(10, 21);
for (int i = 0; i < lineCount; i++)
{
var prop = lineColors[r.Next(lineColors.Length)];
Pen pen = (Pen)prop.GetValue(penObj, null);
g.DrawLine(pen,
new PointF((float)(r.NextDouble() * CharWidth * count), (float)(r.NextDouble() * CharHeight)),
new PointF((float)(r.NextDouble() * CharWidth * count), (float)(r.NextDouble() * CharHeight)));
}
g.Dispose();
context.Response.Clear();
context.Response.ContentType = "image/jpeg";
bmp.Save(context.Response.OutputStream, ImageFormat.Jpeg);
bmp.Dispose();
context.Response.End();
}
public bool IsReusable => false;
}
VB.NET 版
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Reflection
Imports System.Web
Public Class ValidationCode
Implements IHttpHandler
Shared r As New Random(Guid.NewGuid().GetHashCode())
Shared colors As PropertyInfo() = GetType(Brushes).GetProperties(
BindingFlags.Public Or BindingFlags.Static).Where(Function(p) p.Name <> "Black" AndAlso p.Name <> "Transparent").ToArray()
Shared lineColors As PropertyInfo() = GetType(Pens).GetProperties(
BindingFlags.Public Or BindingFlags.Static).Where(Function(p) p.Name <> "Black").ToArray()
Shared brushObj As Object = GetType(Brushes).GetConstructor(BindingFlags.NonPublic, Nothing, Type.EmptyTypes, Nothing)
Shared penObj As Object = GetType(Pens).GetConstructor(BindingFlags.NonPublic, Nothing, Type.EmptyTypes, Nothing)
Const CharWidth As Single = 40F
Const CharHeight As Single = 50F
Public Sub ProcessRequest(context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim count As Integer = 5
Integer.TryParse(context.Request.QueryString("reqNum"), count)
Dim bmp As New Bitmap(CInt(CharWidth * count), CInt(CharHeight))
Dim g As Graphics = Graphics.FromImage(bmp)
Dim code As String = ""
For i = 1 To count
code += r.Next(0, 9).ToString()
Dim prop = colors(r.Next(colors.Length))
Dim brush As Brush = DirectCast(prop.GetValue(brushObj, Nothing), Brush)
g.DrawString(code(i-1).ToString(), New Font("黑體", CharWidth), brush, New PointF((i-1)*CharWidth, 0))
Next
Dim lineCount = r.Next(10, 21)
For i = 1 To lineCount
Dim prop = lineColors(r.Next(lineColors.Length))
Dim pen As Pen = DirectCast(prop.GetValue(penObj, Nothing), Pen)
g.DrawLine(pen,
New PointF(CSng(r.NextDouble() * CharWidth * count), CSng(r.NextDouble() * CharHeight)),
New PointF(CSng(r.NextDouble() * CharWidth * count), CSng(r.NextDouble() * CharHeight)))
Next
g.Dispose()
context.Response.Clear()
context.Response.ContentType = "image/jpeg"
bmp.Save(context.Response.OutputStream, ImageFormat.Jpeg)
bmp.Dispose()
context.Response.End()
End Sub
Public ReadOnly Property IsReusable As Boolean Implements IHttpHandler.IsReusable
Get : Return False : End Get
End Property
End Class
前台引用與換圖
<img src="/ValidationCode.ashx?reqNum=10" id="imgSD" />
<input type="button" value="換一張" onclick="ChangeSD()" />
<script>
function ChangeSD() {
var img = document.getElementById("imgSD");
img.src = ""; // 清空觸發重新請求
img.src = "/ValidationCode.ashx?reqNum=10";
}
</script>

設計細節說明
- 靜態反射快取:
colors/lineColors/brushObj/penObj 為 static,避免每次請求反射效能損耗
- 排除顏色:背景為黑色,故排除
Brushes.Black/Transparent;線條排除黑色
- 隨機種子:
Guid.NewGuid().GetHashCode() 確保高熵值
- 雙次設 src:瀏覽器對相同 URL 不重新請求,先清空再設回原 URL 強制重載