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.
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>
@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");
}
{
//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");
}