Installed Packages
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0010" />
TreeView
/Common
- Captcha.cs
- CaptchaManager.cs
/Controllers
- HomeController.cs
/Controllers/Api
- CaptchaController.cs
/ViewModels/Home
- IndexViewModel.cs
/Views/Home
- Index.cshtml
// Captcha.cs
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.IO;
using System.Text;
namespace ApsNetCoreCaptchaSample.Common
{
public class Captcha : IDisposable
{
private static char[] _charset = null;
private static char[] charset
{
get
{
if (_charset == null)
{
var sb = new StringBuilder();
for (int i = 2; i < 10; i++)
{
sb.Append(i.ToString());
}
for (int c = 'a'; c < 'z'; c++)
{
if (c == 'i' || c == 'x' || c == 'w'
|| c == 'o' || c == 'l' || c == 'u'
|| c == 'j' || c == 'z' || c == 'm'
|| c == 'c')
continue;
sb.Append((char)c);
}
for (int c = 'A'; c < 'Z'; c++)
{
if (c == 'I' || c == 'X' || c == 'W'
|| c == 'O' || c == 'U' || c == 'M'
|| c == 'J' || c == 'Z' || c == 'C')
continue;
sb.Append((char)c);
}
_charset = sb.ToString().ToCharArray();
}
return _charset;
}
}
public DateTimeOffset CreateDate { get; }
public Guid Id { get; }
private string Text { get; }
public byte[] Image { get; private set; }
public string ToBase64()
{
if (Image == null)
return string.Empty;
return Convert.ToBase64String(Image);
}
public bool Confirm(string text)
{
if (string.IsNullOrWhiteSpace(text))
return false;
return text == Text;
}
public Captcha()
{
Text = GenerateRandomText();
Image = DrawCaptcha(Text);
Id = Guid.NewGuid();
CreateDate = DateTimeOffset.UtcNow;
}
private readonly static string[] fonts = new string[3] {
"Arial", "Verdana", "Times New Roman" };
private byte[] DrawCaptcha(string text)
{
byte[] result = null;
float position = 0;
using (var img = new Image<Rgba32>(90, 40))
{
var x = 20;
var y = 8;
var backColor = Color.White;
img.Mutate(ctx => ctx.BackgroundColor(Color.White));
float maxX = 0;
for (int i = 0; i < text.Length; i++)
{
var c = text[i];
var random = new Random();
var font = SystemFonts.CreateFont(
fonts[random.Next(0, fonts.Length - 1)],
random.Next(24, 28),
random.Next(0, 1) == 0 ? FontStyle.Regular : FontStyle.Italic);
var location = new PointF(x + position, y + random.Next(0, 4));
img.Mutate(ctx => ctx.DrawText(c.ToString(), font, Color.Black, location));
position += TextMeasurer.Measure(c.ToString(), new RendererOptions(font, location)).Width - 4;
maxX = position;
var builder = new AffineTransformBuilder();
img.Mutate(ctx => ctx.Transform(
builder.PrependRotationDegrees((float)(random.NextDouble()), new PointF((float)2, (float)4))));
}
for (int i = 0; i < 2; i++)
{
var r = new Random();
img.Mutate(ctx => ctx.DrawLines(Color.Black, 2
, new PointF[] { new PointF(r.Next(20, 26), r.Next(8, 36)),
new PointF(r.Next(Convert.ToInt32(maxX+1),Convert.ToInt32(89)), r.Next(8, 36)) }));
}
using (var ms = new MemoryStream())
{
img.SaveAsPng(ms);
result = ms.ToArray();
}
}
return result;
}
private string GenerateRandomText()
{
var sb = new StringBuilder();
for (int i = 0; i < 5; i++)
{
var random = new Random();
var index = random.Next(0, charset.Length - 1);
sb.Append(charset[index]);
}
return sb.ToString();
}
public void Dispose()
{
Image = null;
}
}
}
// CaptchaManager
using System;
using System.Collections.Generic;
using System.Linq;
namespace ApsNetCoreCaptchaSample.Common
{
public static class CaptchaManager
{
private static readonly List<Captcha> captchas = new List<Captcha>();
public static (Guid Id, string Image) New()
{
using (var captcha = new Captcha())
{
captchas.Add(captcha);
return (captcha.Id, captcha.ToBase64());
}
}
public static bool Confirm(Guid id, string text)
{
captchas.RemoveAll(q => q.CreateDate < DateTimeOffset.UtcNow.AddMinutes(-2));
var captcha = captchas.SingleOrDefault(q => q.Id == id);
if (captcha != null && captcha.Confirm(text))
{
captchas.Remove(captcha);
return true;
}
return false;
}
}
}
// CaptchaController.cs
using ApsNetCoreCaptchaSample.Common;
using Microsoft.AspNetCore.Mvc;
namespace ApsNetCoreCaptchaSample.Controllers.Api
{
[Route("api/[controller]")]
[ApiController]
public class CaptchaController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
var captcha = CaptchaManager.New();
var response = new
{
id = captcha.Id,
image = captcha.Image
};
return Ok(response);
}
}
}
// HomeController.cs
using ApsNetCoreCaptchaSample.Common;
using ApsNetCoreCaptchaSample.ViewModels.Home;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace ApsNetCoreCaptchaSample.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
[HttpPost]
public string Index(IndexViewModel model)
{
return CaptchaManager.Confirm(model.CaptchaId, model.Captcha).ToString();
}
}
}
// IndexViewModel.cs
using System;
namespace ApsNetCoreCaptchaSample.ViewModels.Home
{
public class IndexViewModel
{
public string Captcha { get; set; }
public Guid CaptchaId { get; set; }
}
}
// Index.cshtml
@model ApsNetCoreCaptchaSample.ViewModels.Home.IndexViewModel
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Captcha Sample</title>
</head>
<body>
<form method="post">
<img id="captchaImage"/>
@Html.TextBoxFor(q => q.Captcha, new { placeholder = "Captcha" })
<br>
<input type="submit" id="send" name="send" value="Send">
@Html.HiddenFor(q => q.CaptchaId)
</form>
<script>
var captchaImage = document.getElementById('captchaImage');
var captchaId = document.getElementById('CaptchaId');
function newCaptcha() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/captcha', true)
xhr.onreadystatechange = function () {
if (this.readyState != 4) return;
if (this.status == 200) {
var resp = JSON.parse(this.responseText);
captchaId.value = resp.id;
captchaImage.src = 'data:image/png;base64, ' + resp.image;
}
}
xhr.send();
}
captchaImage.addEventListener('click', function () {
newCaptcha();
});
newCaptcha();
</script>
</body>
</html>