In this post I will show you how to add CAPTCHA functionality to a html form in an Asp.Net MVC 4 project. My goal is to make the CAPTCHA problem easy enough for all to solve, like a simple sum operation, and easier to read then the standard CAPTCHA text. An easy to read image is more vulnerable to smart bots that have an ORC system but I prefer to scare less clients then to provide the strongest anti-bot protection. And one more feature, when clicked the image should, change giving the users a new chance to respond correctly.
Implementing CAPTCHA in C# and MVC 4 takes these steps:
If you want to use multiple CAPTCHAs you can use the prefix to store the answer for each form. Much can be improved regarding the rendered image, for example I could use different font and size for each number in the equation, replace the noise with text distortion.
In the View, beside a label, textbox and validator span you’ll need to add an image placeholder for the CAPTCHA.
Instead of validating the CAPTCHA on post, you can use an Ajax call to validate and refresh the image asynchronous, more details on how you can send forms data over Ajax read this blog post.
Implementing CAPTCHA in C# and MVC 4 takes these steps:
- Create an Action that returns a CAPTCHA image and stores in the user session the right answer
- Add to your Model a string property named Captcha
- Add to your View the textbox for Captcha and the image placeholder
- Validate answer inside your own Action
Render CAPTCHA image
CaptchaController.cs
public ActionResult CaptchaImage(string prefix, bool noisy = true)
{
var rand = new Random((int)DateTime.Now.Ticks);
//generate new question
int a = rand.Next(10, 99);
int b = rand.Next(0, 9);
var captcha = string.Format("{0} + {1} = ?", a, b);
//store answer
Session["Captcha" + prefix] = a + b;
//image stream
FileContentResult img = null;
using (var mem = new MemoryStream())
using (var bmp = new Bitmap(130, 30))
using (var gfx = Graphics.FromImage((Image)bmp))
{
gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
gfx.SmoothingMode = SmoothingMode.AntiAlias;
gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height));
//add noise
if (noisy)
{
int i, r, x, y;
var pen = new Pen(Color.Yellow);
for (i = 1; i < 10; i++)
{
pen.Color = Color.FromArgb(
(rand.Next(0, 255)),
(rand.Next(0, 255)),
(rand.Next(0, 255)));
r = rand.Next(0, (130 / 3));
x = rand.Next(0, 130);
y = rand.Next(0, 30);
gfx.DrawEllipse(pen, x – r, y – r, r, r);
}
}
//add question
gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3);
//render as Jpeg
bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg);
img = this.File(mem.GetBuffer(), "image/Jpeg");
}
return img;
}
{
var rand = new Random((int)DateTime.Now.Ticks);
//generate new question
int a = rand.Next(10, 99);
int b = rand.Next(0, 9);
var captcha = string.Format("{0} + {1} = ?", a, b);
//store answer
Session["Captcha" + prefix] = a + b;
//image stream
FileContentResult img = null;
using (var mem = new MemoryStream())
using (var bmp = new Bitmap(130, 30))
using (var gfx = Graphics.FromImage((Image)bmp))
{
gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
gfx.SmoothingMode = SmoothingMode.AntiAlias;
gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height));
//add noise
if (noisy)
{
int i, r, x, y;
var pen = new Pen(Color.Yellow);
for (i = 1; i < 10; i++)
{
pen.Color = Color.FromArgb(
(rand.Next(0, 255)),
(rand.Next(0, 255)),
(rand.Next(0, 255)));
r = rand.Next(0, (130 / 3));
x = rand.Next(0, 130);
y = rand.Next(0, 30);
gfx.DrawEllipse(pen, x – r, y – r, r, r);
}
}
//add question
gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3);
//render as Jpeg
bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg);
img = this.File(mem.GetBuffer(), "image/Jpeg");
}
return img;
}
Include CAPTCHA validator in Model and View
Models.cs
public class SubscribeModel
{
//model specific fields
[Required]
[Display(Name = "How much is")]
public string Captcha { get; set; }
}
{
//model specific fields
[Required]
[Display(Name = "How much is")]
public string Captcha { get; set; }
}
Index.cshtml
@*form specific fields*@
<div class="editor-label">
@Html.LabelFor(model => model.Captcha)
<a href="@Url.Action("Index")">
<img alt="Captcha" src="@Url.Action("CaptchaImage")" style="" />
</a>
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Captcha)
@Html.ValidationMessageFor(model => model.Captcha)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Captcha)
<a href="@Url.Action("Index")">
<img alt="Captcha" src="@Url.Action("CaptchaImage")" style="" />
</a>
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Captcha)
@Html.ValidationMessageFor(model => model.Captcha)
</div>
Validate CAPTCHA on the server side
Inside your post action where the form submits you can validate the answer by comparing with the session value.
CaptchaController.cs
[HttpPost]
public ActionResult Index(SubscribeModel model)
{
//validate captcha
if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha)
{
ModelState.AddModelError("Captcha", "Wrong value of sum, please try again.");
//dispay error and generate a new captcha
return View(model);
}
return RedirectToAction("ThankYouPage");
}
public ActionResult Index(SubscribeModel model)
{
//validate captcha
if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha)
{
ModelState.AddModelError("Captcha", "Wrong value of sum, please try again.");
//dispay error and generate a new captcha
return View(model);
}
return RedirectToAction("ThankYouPage");
}
No comments:
Post a Comment