问题
有种不太幸运的情况,有人用自动程序去提交表单,在整个互联网中造成大量的垃圾。为了防止这种情况的方法之一,是使用一个验证码---CAPTCHA:全自动区分计算机和人类的图灵测试,这迫使用户把生成的文字输入到文本框。
(译者:CAPTCHA是一种更人性化的验证码,可以通过视觉和听觉来区分post的请求是人类还是计算机发出的)
解决方案
从NuGet安装ASP.NET Web Helpers Library 从而在BookCommentsController实现防止而已添加书评的功能。
讨论
需要安装一个新的类包,使在表单上应用CAPTCHA成为可能。微软已经创建了一个NuGet web helpers 类包含了CAPTCHA,让我们很容易实施并且验证用户输入的CAPTCHA。先打开MVC项目,在vs中选择Tools→LibraryPackage Manager→Add Library Package Reference。点击左边的Online,在第一页的下方您就可以发现 Asp.net web helpers Library。点击安装。
在我们的例子里。那些自动发送post请求的软件一般会用在图书评论上。所以是这里最完美的添加CAPTCHA的地方。在开始之前你必须在RECAPTCHA website为你的域名注册。(译者:一般要用一个gmail账户。我们使用自己已有的或者重新注册一个,在这里由于我们的项目是在本机练习使用的,我就为我的localhost注册)。注册成功之后你可以得到一个公钥(public key)和一个私钥(private key)。
提示:如果你不使用Ajax去包含CAPTCHA,你可以通过以下两行代码改变你的view:
1
2
|
@using Microsoft.Web.Helpers; @ReCaptcha.GetHtml("<你的公钥>", "<你的私钥>") |
一旦配置完成了,是时候开始更新我们的代码了。我们需要在BookComments/Index view里做一些小更改。这个view是前一段创建的,用于使用ajax提交书评。这个Ajax需要更新成:当请求完毕,调用javascript函数去显示CAPTCHA按钮。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
@model IEnumerable< MvcApplication.Models.BookComment > @{ ViewBag.Title = "Index"; } < h2 > Index</ h2 > < p > @Ajax.ActionLink("Create New", "Create", new { BookId = ViewBag.BookId }, new AjaxOptions { UpdateTargetId = "AddComment" }) </ p > < div id = "AddComment" > </ div > < script type = "text/javascript" src = </ script > < script type = "text/javascript" > function DisplayCaptcha() { Recaptcha.destroy(); Recaptcha.create("6Le27coSAAAAAK8KqpUIGvz3qTDXGa9ud9Xst4yY", "captcha", {});//你的公钥 } </ script > < table > < tr > < th > Comment </ th > < th > Created </ th > </ tr > @foreach (var item in Model) { < tr > < td > @Html.DisplayFor(modelItem => item.Comment) </ td > < td > @Html.DisplayFor(modelItem => item.Created) </ td > < td > @Html.DisplayFor(modelItem => item.Book.Title) </ td > </ tr > } </ table > |
现在,去更新BookComments/create view 。首先添加一个地点去展示CAPTCHA.然后添加一个新的HTML 错误消息,当他们输入错误的验证码时,会提示错误。最后在ReloadComment javascript 函数里更改代码成不自动reload 书评(仅仅当没错的时候才reload)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
@model MvcApplication.Models.BookComment @{ ViewBag.Title = "Create"; } < h2 > Create</ h2 > @section JavascriptAndCSS { < script src = "@Url.Content(" ~/Scripts/jquery.validate.min.js")" type = "text/javascript" ></ script > < script src=" @Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type = "text/javascript" ></ script > } < script type = "text/javascript" > function ReloadComments() { var reload = "@ViewBag.RefreshComments"; if (reload == "False") { DisplayCaptcha(); } else { $("#Comments").load("/BookComments/Index?BookId=@ViewBag.BookId"); } } </ script > @using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "AddComment", OnComplete = "ReloadComments" })) { @Html.Hidden("BookId", (int)ViewBag.BookId); @Html.ValidationSummary(true) < fieldset > < legend >BookComment</ legend > < div class = "editor-label" > @Html.LabelFor(model => model.Comment) </ div > < div class = "editor-field" > @Html.TextAreaFor(model => model.Comment) @Html.ValidationMessageFor(model => model.Comment) </ div > < div class = "editor-label" > Are you human? </ div > < div class = "editor-field" > < div id = "captcha" > </ div > @Html.ValidationMessage("Captcha") </ div > < p > < input type = "submit" value = "Create" /> </ p > </ fieldset > } |
最后我们要更新BookCommentsController 去验证输入的CAPTCHA。如果验证不合法,我们就把错误消息添加到ModelState里去,view把它展示出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Mvc; using MvcApplication.Models; using Microsoft.Web.Helpers; using MvcApplication.Models; namespace MvcApplication.Controllers { public class BookCommentsController : Controller { private BookDBContext db = new BookDBContext(); // // GET: /BookComments/ public ActionResult Index(int BookId) { ViewBag.BookId = BookId; var bookcomments = db.BookComments.Include( b => b.Book).Where(b => b.BookId == BookId); return PartialView(bookcomments.ToList()); } // // GET: /BookComments/Create public ActionResult Create(int BookId) { ViewBag.BookId = BookId; ViewBag.RefreshComments = false; return PartialView(); } // // POST: /BookComments/Create [HttpPost] public ActionResult Create(BookComment bookcomment) { ViewBag.RefreshComments = false; var captchaSuccess = ReCaptcha.Validate( "6Le27coSAAAAAM6kZnXU8m1j9");//你的私钥 if (ModelState.IsValid && captchaSuccess) { bookcomment.Created = DateTime.Now; db.BookComments.Add(bookcomment); db.SaveChanges(); ViewBag.RefreshComments = true; } // if captcha failed add error message if (!captchaSuccess) { ModelState.AddModelError("Captcha", "Invalid CAPTCHA"); } ViewBag.BookId = bookcomment.BookId; return PartialView(bookcomment); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); } } } |
(译者:下图是我实践之后的截图,不知道这个CAPTCHA的背景样式是否能自定义,如果可以的话就太酷了!)