در این مطلب، سعی خواهیم کرد تا همانند تصویر امنیتی این سایت که موقع ورود
نمایش داده میشود، یک نمونه مشابه به آنرا در ASP.Net MVC ایجاد کنیم.
ذکر این نکته ضروری است که قبلا آقای پایروند در
یک مطلب دو قسمتی کاری مشابه را انجام داده بودند، اما در مطلبی که در اینجا ارائه شده سعی کرده ایم تا تفاوتهایی را با مطلب ایشان داشته باشد.
همان طور که ممکن است بدانید، اکشن متدها در کنترلرهای MVC میتوانند انواع
مختلفی را برگشت دهند که شرح آن در مطالب این سایت به مفصل گذشته است. یکی
از این انواع، نوع ActionResult میباشد. این یک کلاس پایه برای انواع
برگشتی توسط اکشن متدها مثل JsonResult، FileResult میباشد. (
اطلاعات بیشتر را اینجا بخوانید) اما ممکن است
مواقعی پیش بیاید که بخواهید نوعی را توسط یک اکشن متد برگشت دهید که به
صورت توکار تعریف نشده باشد. مثلا زمانی را در نظر بگیرید که بخواهید یک
تصویر امنیتی را برگشت دهید. یکی از راه حلهای ممکن به این صورت است که
کلاسی ایجاد شود که از کلاس پایه ActionResult ارث بری کرده باشد. بدین
صورت:
using System;
using System.Web.Mvc;
namespace MVCPersianCaptcha.Models
{
public class CaptchaImageResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
throw new NotImplementedException();
}
}
}
همان طور که مشاهده میکنید، کلاسی به اسم CaptchaImageResult تعریف شده که از کلاس ActionResult ارث بری کرده است. در این صورت باید متد ExecuteResult را override کنید. متد ExecuteResult به صورت خودکار هنگامی که از CaptchaImageResult به عنوان یک نوع برگشتی اکشن متد استفاده شود اجرا میشود. به همین خاطر باید تصویر امنیتی توسط این متد تولید شود و به صورت جریان (stream) برگشت داده شود
کدهای اولیه برای ایجاد یک تصویر امنیتی به صورت خیلی ساده از کلاسهای
فراهم شده توسط +GDI ، که در دات نت فریمورک وجود دارند استفاده خواهند
کرد. برای این کار ابتدا یک شیء از کلاس Bitmap با دستور زیر ایجاد خواهیم
کرد:
// Create a new 32-bit bitmap image.
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
پارامترهای اول و دوم به ترتبی عرض و ارتفاع تصویر امنیتی را مشخص خواهند کرد و پارامتر سوم نیز فرمت تصویر را بیان کرده است. Format32bppArgb یعنی یک تصویر که هر کدام از پیکسلهای آن 32 بیت فضا اشغال خواهند کرد ، 8 بیت اول میزان آلفا، 8 بیت دوم میزان رنگ قرمز، 8 بیت سوم میزان رنگ سبز، و 8 تای آخر نیز میزان رنگ آبی را مشخص خواهند کرد
سپس شیئی از نوع Graphics برای انجام عملیات ترسیم نوشتههای فارسی روی شیء bitmap ساخته میشود:
// Create a graphics object for drawing.
Graphics gfxCaptchaImage = Graphics.FromImage(bitmap);
خصوصیات مورد نیاز ما از gfxCaptchaImage را به صورت زیر مقداردهی میکنیم:
gfxCaptchaImage.PageUnit = GraphicsUnit.Pixel;
gfxCaptchaImage.SmoothingMode = SmoothingMode.HighQuality;
gfxCaptchaImage.Clear(Color.White);
واحد اندازه گیری به پیکسل، کیفیت تصویر تولید شده توسط دو دستور اول، و در دستور سوم ناحیه ترسیم با یک رنگ سفید پاک میشود.
سپس یک عدد اتفاقی بین 1000 و 9999 با دستور زیر تولید میشود:
// Create a Random Number from 1000 to 9999
int salt = CaptchaHelpers.CreateSalt();
متد CreateSalt در کلاس CaptchaHelpers قرار گرفته است، و نحوه پیاده سازی آن بدین صورت است:
public int CreateSalt()
{
Random random = new Random();
return random.Next(1000, 9999);
}
سپس مقدار موجود در salt را برای مقایسه با مقداری که کاربر وارد کرده است در session قرار میدهیم:
HttpContext.Current.Session["captchastring"] = salt;
سپس عدد اتفاقی تولید شده باید تبدیل به حروف شود، مثلا اگر عدد 4524 توسط
متد CreateSalt تولید شده باشد، رشته "چهار هزار و پانصد و بیست و چهار"
معادل آن نیز باید تولید شود. برای تبدیل عدد به حروف، آقای نصیری
کلاس خیلی خوبی نوشته اند که چنین کاری را انجام میدهد. ما نیز از همین کلاس استفاده خواهیم کرد:
string randomString = (salt).NumberToText(Language.Persian);
در دستور بالا، متد الحاقی NumberToText با پارامتر Language.Persian وظیفه تبدیل عدد salt را به حروف فارسی معادل خواهد داشت.
به صورت پیش فرض نوشتههای تصویر امنیتی به صورت چپ چین نوشته خواهند شد، و با
توجه به این که نوشته ای که باید در تصویر امنیتی قرار بگیرد فارسی است،
پس بهتر است آنرا به صورت راست به چپ در تصویر بنویسیم، بدین صورت:
// Set up the text format.
var format = new StringFormat();
int faLCID = new System.Globalization.CultureInfo("fa-IR").LCID;
format.SetDigitSubstitution(faLCID, StringDigitSubstitute.National);
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Near;
format.FormatFlags = StringFormatFlags.DirectionRightToLeft;
و همچنین نوع و اندازه فونت که در این مثال tahoma میباشد:
// Font of Captcha and its size
Font font = new Font("Tahoma", 10);
خوب نوشته فارسی اتفاقی تولید شده آماده ترسیم شدن است، اما اگر چنین
تصویری تولید شود احتمال خوانده شدن آن توسط روباتهای پردازش گر تصویر
شاید زیاد سخت نباشد. به همین دلیل باید کاری کنیم تا خواندن این تصویر
برای این روباتها سختتر شود، روشهای مختلفی برای این کار
وجود دارند: مثل ایجاد نویز در تصویر امنیتی یا استفاده از توابع ریاضی
سینوسی و کسینوسی برای نوشتن نوشتهها به صورت موج. برای این کار اول یک
مسیر گرافیکی در تصویر یا موج اتفاقی ساخته شود و به شیء gfxCaptchaImage نسبت
داده شود. برای این کار اول نمونه ای از روی کلاس GraphicsPath ساخته میشود،
// Create a path for text
GraphicsPath path = new GraphicsPath();
و با استفاده از متد AddString ، رشته اتفاقی تولید شده را با فونت مشخص
شده، و تنظیمات اندازه دربرگیرنده رشته مورد نظرر، و تنظیمات فرمت بندی
رشته را لحاظ خواهیم کرد.
path.AddString(randomString,
font.FontFamily,
(int)font.Style,
(gfxCaptchaImage.DpiY * font.SizeInPoints / 72),
new Rectangle(0, 0, width, height), format);
با خط کد زیر شیء path را با رنگ بنقش با استفاده از شیء gfxCaptchaImage روی تصویر bitmap ترسیم خواهیم کرد:
gfxCaptchaImage.DrawPath(Pens.Navy, path);
برای ایجاد یک منحنی و موج از کدهای زیر استفاده خواهیم کرد:
//-- using a sin ware distort the image
int distortion = random.Next(-10, 10);
using (Bitmap copy = (Bitmap)bitmap.Clone())
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int newX = (int)(x + (distortion * Math.Sin(Math.PI * y / 64.0)));
int newY = (int)(y + (distortion * Math.Cos(Math.PI * x / 64.0)));
if (newX < 0 || newX >= width) newX = 0;
if (newY < 0 || newY >= height) newY = 0;
bitmap.SetPixel(x, y, copy.GetPixel(newX, newY));
}
}
}
موقع ترسیم تصویر امنیتی است:
//-- Draw the graphic to the bitmap
gfxCaptchaImage.DrawImage(bitmap, new Point(0, 0));
gfxCaptchaImage.Flush();
تصویر امنیتی به صورت یک تصویر با فرمت jpg به صورت جریان (stream) به مرورگر باید فرستاده شوند:
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = "image/jpeg";
bitmap.Save(response.OutputStream, ImageFormat.Jpeg);
و در نهایت حافظههای اشغال شده توسط اشیاء فونت و گرافیک و تصویر امنیتی آزاد خواهند شد:
// Clean up.
font.Dispose();
gfxCaptchaImage.Dispose();
bitmap.Dispose();
برای استفاده از این کدها، اکشن متدی نوشته میشود که نوع CaptchaImageResult را برگشت میدهد:
public CaptchaImageResult CaptchaImage()
{
return new CaptchaImageResult();
}
اگر در یک View خصیصه src یک تصویر به آدرس این اکشن متد مقداردهی شود، آنگاه تصویر امنیتی تولید شده نمایش پیدا میکند:
<img src="@Url.Action("CaptchaImage")"/>
بعد از پست کردن فرم مقدار text box تصویر امنیتی خوانده شده و با مقدار موجود در session مقایسه میشود، در صورتی که یکسان باشند، کاربر میتواند وارد سایت شود (در صورتی که نام کاربری یا کلمه عبور خود را درست وارد کرده باشد) یا اگر از این captcha در صفحات دیگری استفاده شود عمل مورد نظر میتواند انجام شود. در مثال زیر به طور ساده اگر کاربر در کادر متن مربوط به تصویر امنیتی
مقدار درستی را وارد کرده باشد یا نه، پیغامی به او نشان داده میشود.
[HttpPost]
public ActionResult Index(LogOnModel model)
{
if (!ModelState.IsValid) return View(model);
if (model.CaptchaInputText == Session["captchastring"].ToString())
TempData["message"] = "تصویر امنتی را صحیح وارد کرده اید";
else
TempData["message"] = "تصویر امنیتی را اشتباه وارد کرده اید";
return View();
}
کدهای کامل مربوط به این مطلب را به همراه یک مثال از لینک زیر دریافت نمائید:
MVC-Persian-Captcha