最近因为有个项目除了登录还有其他很多地方需要用到验证码的功能,所以想到了采用HtmlHelper和ActionFilter封装一个验证码的功能,以便能够重复调用。封装好以后调用很方便,只需在View中调用Html扩展好的方法,相应的Action加上验证功能的Filter就行了。
首先写一个能够随机生成数字的图片的类,园子里有一大把这样的文章,直接拿过来就用了,自己懒得写了。
1 public class CaptchaRender 2 { 3 public CaptchaRender() 4 { 5 } 6 7 /// <summary> 8 /// 验证码的最大长度 9 /// </summary> 10 public int MaxLength 11 { 12 get { return 10; } 13 } 14 15 /// <summary> 16 /// 验证码的最小长度 17 /// </summary> 18 public int MinLength 19 { 20 get { return 1; } 21 } 22 23 /// <summary> 24 /// 生成验证码 25 /// </summary> 26 /// <param name="length">指定验证码的长度</param> 27 /// <returns></returns> 28 public string CreateValidateCode(int length) 29 { 30 int[] randMembers = new int[length]; 31 int[] validateNums = new int[length]; 32 string validateNumberStr = ""; 33 //生成起始序列值 34 int seekSeek = unchecked((int) DateTime.Now.Ticks); 35 Random seekRand = new Random(seekSeek); 36 int beginSeek = (int) seekRand.Next(0, Int32.MaxValue - length*10000); 37 int[] seeks = new int[length]; 38 for (int i = 0; i < length; i++) 39 { 40 beginSeek += 10000; 41 seeks[i] = beginSeek; 42 } 43 //生成随机数字 44 for (int i = 0; i < length; i++) 45 { 46 Random rand = new Random(seeks[i]); 47 int pownum = 1*(int) Math.Pow(10, length); 48 randMembers[i] = rand.Next(pownum, Int32.MaxValue); 49 } 50 //抽取随机数字 51 for (int i = 0; i < length; i++) 52 { 53 string numStr = randMembers[i].ToString(); 54 int numLength = numStr.Length; 55 Random rand = new Random(); 56 int numPosition = rand.Next(0, numLength - 1); 57 validateNums[i] = Int32.Parse(numStr.Substring(numPosition, 1)); 58 } 59 //生成验证码 60 for (int i = 0; i < length; i++) 61 { 62 validateNumberStr += validateNums[i].ToString(); 63 } 64 return validateNumberStr; 65 } 66 67 /// <summary> 68 /// 创建验证码的图片 69 /// </summary> 70 /// <param name="containsPage">要输出到的page对象</param> 71 /// <param name="validateNum">验证码</param> 72 public byte[] CreateValidateGraphic(string validateCode) 73 { 74 Bitmap image = new Bitmap((int) Math.Ceiling(validateCode.Length*12.0), 22); 75 Graphics g = Graphics.FromImage(image); 76 try 77 { 78 //生成随机生成器 79 Random random = new Random(); 80 //清空图片背景色 81 g.Clear(Color.White); 82 //画图片的干扰线 83 for (int i = 0; i < 25; i++) 84 { 85 int x1 = random.Next(image.Width); 86 int x2 = random.Next(image.Width); 87 int y1 = random.Next(image.Height); 88 int y2 = random.Next(image.Height); 89 g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2); 90 } 91 Font font = new Font("Arial", 12, (FontStyle.Bold | FontStyle.Italic)); 92 LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), 93 Color.Blue, Color.DarkRed, 1.2f, true); 94 g.DrawString(validateCode, font, brush, 3, 2); 95 //画图片的前景干扰点 96 for (int i = 0; i < 100; i++) 97 { 98 int x = random.Next(image.Width); 99 int y = random.Next(image.Height); 100 image.SetPixel(x, y, Color.FromArgb(random.Next())); 101 } 102 //画图片的边框线 103 g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); 104 //保存图片数据 105 MemoryStream stream = new MemoryStream(); 106 image.Save(stream, ImageFormat.Jpeg); 107 //输出图片流 108 return stream.ToArray(); 109 } 110 finally 111 { 112 g.Dispose(); 113 image.Dispose(); 114 } 115 } 116 117 /// <summary> 118 /// 得到验证码图片的长度 119 /// </summary> 120 /// <param name="validateNumLength">验证码的长度</param> 121 /// <returns></returns> 122 public static int GetImageWidth(int validateNumLength) 123 { 124 return (int) (validateNumLength*12.0); 125 } 126 127 /// <summary> 128 /// 得到验证码的高度 129 /// </summary> 130 /// <returns></returns> 131 public static double GetImageHeight() 132 { 133 return 22.5; 134 } 135 }
然后写HtmlHelper类型的扩展方法,以便在View中调用。
1 public static class HtmlExtensions 2 { 3 /// <summary> 4 /// 生成验证码 5 /// </summary> 6 /// <param name="helper">当前View的HtmlHelper</param> 7 /// <param name="urlHelper">当前View的UrlHelper</param> 8 /// <returns>带验证码的Img</returns> 9 public static MvcHtmlString GenerateCaptcha(this HtmlHelper helper, UrlHelper urlHelper) 10 { 11 var sb = new StringBuilder(); 12 var builder = new TagBuilder("img"); 13 builder.Attributes.Add("id", "captcha"); 14 builder.Attributes.Add("style", "cursor:pointer"); 15 builder.Attributes.Add("src", urlHelper.Action("GetCaptcha", "Common")); 16 builder.Attributes.Add("alt", "单击刷新验证码"); 17 sb.AppendLine(builder.ToString(TagRenderMode.Normal)); 18 19 sb.AppendLine("<script>"); 20 sb.AppendLine("$(function(){"); 21 sb.AppendLine("$('#captcha').bind('click',function(){this.src='" + 22 urlHelper.Action("GetCaptcha", "Common") + "?time='+(new Date()).getTime()})"); 23 sb.AppendLine("})"); 24 sb.AppendLine("</script>"); 25 26 return MvcHtmlString.Create(sb.ToString()); 27 } 28 29 /// <summary> 30 /// 生成验证码 31 /// </summary> 32 /// <typeparam name="TModel">Model</typeparam> 33 /// <typeparam name="TValue">Model的值</typeparam> 34 /// <param name="helper">当前View的HtmlHelper</param> 35 /// <param name="expression">Model属性的Lambda表达式</param> 36 /// <param name="urlHelper">当前View的UrlHelper</param> 37 /// <returns>封装好的label,textbox,带验证码的img</returns> 38 public static MvcHtmlString GenerateCaptcha<TModel, TValue>(this HtmlHelper<TModel> helper, 39 Expression<Func<TModel, TValue>> expression, UrlHelper urlHelper) 40 { 41 StringBuilder sb = new StringBuilder(); 42 var label = helper.LabelFor(expression, new {}, ":"); 43 var textbox = helper.TextBoxFor(expression); 44 var captcha = GenerateCaptcha(helper, urlHelper); 45 sb.AppendLine(label.ToHtmlString()); 46 sb.AppendLine(textbox.ToHtmlString()); 47 sb.AppendLine(captcha.ToHtmlString()); 48 49 return MvcHtmlString.Create(sb.ToString()); 50 } 51 }
其中builder.Attributes.Add("src", urlHelper.Action("GetCaptcha", "Common"))调用了用于生成带验证码的GetCaptcha方法,该方法后面会提到,本人写在
CommonController当中,GetCaptcha方法其实就是调用了上面的CaptchaRender类中的CreateValidateCode方法,生成验证码输出到View。
GenerateCaptcha<TModel, TValue>这个泛型方法可以绑定视图模型中验证码的字段,并且生成label,textbox,image标签,还有相关的脚本,在View中输出的内容如下:
<label for="Captcha">验证码:</label> <input id="Captcha" name="Captcha" type="text" value="" /> <img alt="单击刷新验证码" id="captcha" src="/Common/GetCaptcha" style="cursor:pointer"></img> <script> $(function(){ $('#captcha').bind('click',function(){this.src='/Common/GetCaptcha?time='+(new Date()).getTime()}) }) </script>
CommonController中的GetCaptcha方法如下:
/// <summary> /// 生成验证码 /// </summary> /// <returns></returns> public ActionResult GetCaptcha() { CaptchaRender captcha = new CaptchaRender(); string code = captcha.CreateValidateCode(5); TempData["Captcha"] = code; byte[] bytes = captcha.CreateValidateGraphic(code); return File(bytes, "image/jpeg"); }
TempData["Captcha"] = code是把生成的验证码放到TempData中,以便在ActionFilter中获取到验证码的值,ActionFilter方法如下:
1 public class CaptchaValidatorAttribute : ActionFilterAttribute 2 { 3 private const string CaptchaFormValue = "Captcha"; 4 public override void OnActionExecuting(ActionExecutingContext filterContext) 5 { 6 bool valid = false; 7 8 foreach (var value in filterContext.HttpContext.Request.Form.AllKeys) 9 { 10 if (value.Contains(CaptchaFormValue)) 11 { 12 valid = (string) filterContext.Controller.TempData["Captcha"] == 13 filterContext.HttpContext.Request.Form[value]; 14 break; 15 } 16 } 17 filterContext.ActionParameters["captchaValid"] = valid; 18 base.OnActionExecuting(filterContext); 19 } 20 }
CaptchaValidator过滤器其实就是在相应的Action执行前,遍历Form窗体变量集合的所有Key值,把保存在TempData["Captcha"]中的验证码的值和Form窗体中name="Captcha"(Key值="Captcha")的Textbox的值(用户输入的验证码)比较,然后再把比较后的bool值赋值给用CaptchaValidator特性修饰的Action的captchaValid参数。(Action根据captchaValid参数的值去判断是否通过验证)。
View视图代码调用如下:
@Html.GenerateCaptcha(m => m.Captcha, Url)
Action调用如下:
一定要记得Action的参数名称captchaValid和过滤器中 filterContext.ActionParameters["captchaValid"]一致。
效果图如下: