|
|

Introduction
I'm sure everyone reading this is familiar with spam. There are two schools of thought when it comes to fighting spam:
- Bayesian filtering, such as POPFile.
- Challenge/response human verification, such as SpamArrest.
Both of these approaches have their pros and cons, of course. This article will only deal with the second technique: verifying that the data you are receiving is coming from an actual human being and not a robot or script. A CAPTCHA is a way of testing input to ensure that you're dealing with a human. Now, there are a lot of ways to build a CAPTCHA, as documented in this MSDN article on the subject, but I will be focusing on a visual data entry CAPTCHA.
There's already a great ASP.NET article on a CAPTCHA control here on CodeProject, so you may be wondering what this article is for. I wanted to rebuild that solution for the following reasons:
- more control settings and flexibility
- conversion to my preferred VB.NET language
- abstracted into a full blown ASP.NET server control.
So, this article will document how to turn a set of existing ASP.NET web pages into a simple, drag and drop ASP.NET server control -- with a number of significant enhancements along the way.
Implementation
The first thing I had to deal with was the image generated by the CAPTCHA class. This was originally done with a dedicated .aspx form-- something that won't exist for a server control. How could I generate an image on the fly? After some research, I was introduced to the world of HttpModules and HttpHandlers. They are extremely powerful -- and a single HttpHandler solves this problem neatly.
All we need is a small Web.config modification in the <system.web> section:
<httpHandlers>
<add verb="GET" path="CaptchaImage.aspx"
type="WebControlCaptcha.CaptchaImageHandler, WebControlCaptcha" />
</httpHandlers>
This handler defines a special page named CaptchaImage.aspx. Now, this "page" doesn't actually exist. When a request for CaptchaImage.aspx occurs, it will be intercepted and handled by a class that implements the IHttpHandler interface: CaptchaImageHandler. Here's the relevant code section:
Public Sub ProcessRequest(ByVal context As System.Web.HttpContext) _
Implements System.Web.IHttpHandler.ProcessRequest
Dim app As HttpApplication = context.ApplicationInstance
Dim strGuid As String = Convert.ToString(app.Request.QueryString("guid"))
Dim ci As CaptchaImage
If strGuid = "" Then
ci = New CaptchaImage
Else
ci = CType(app.Context.Cache(strGuid), CaptchaImage)
app.Context.Cache.Remove(strGuid)
End If
ci.Image.Save(app.Context.Response.OutputStream, _
Drawing.Imaging.ImageFormat.Jpeg)
app.Response.ContentType = "image/jpeg"
app.Response.StatusCode = 200
app.Response.End()
End Sub
A new CAPTCHA image will be generated, and the image streamed directly to the browser from memory. Problem solved!
However, there's another problem. There has to be communication between the HttpHandler responsible for displaying the image, and the web page hosting the control -- otherwise, how would the calling control know what the randomly generated CAPTCHA text was? If you view source on the rendered control, you'll see that a GUID is passed in through the querystring:
<img src="CaptchaImage.aspx?guid=99fecb18-ba00-4b60-9783-37225179a704"
border='0'>
This GUID (globally unique identifier) is a key used to access a CAPTCHA object that was originally stored in the ASP.NET Cache by the control. Take a look at the CaptchaControl.GenerateNewCaptcha method:
Private Sub GenerateNewCaptcha()
LocalGuid = Guid.NewGuid.ToString
If Not IsDesignMode Then
HttpContext.Current.Cache.Add(LocalGuid, _captcha, Nothing, _
DateTime.Now.AddSeconds(HttpContext.Current.Session.Timeout), _
TimeSpan.Zero, Caching.CacheItemPriority.NotRemovable, Nothing)
End If
Me.CaptchaText = _captcha.Text
Me.GeneratedAt = Now
End Sub
It may seem a little strange, but it works great! The sequence of ASP.NET events is as follows:
- Page is rendered.
- Page calls
CaptchaControl1.OnPreRender . This generates a new GUID and a new CAPTCHA object reflecting the control properties. The resulting CAPTCHA object is stored in the Cache by GUID.
- Page calls
CaptchaControl1.Render; the special <img> tag URL is written to the browser.
- Browser attempts to retrieve the special
<img> tag URL.
CaptchaImageHandler.ProcessRequest fires. It retrieves the GUID from the querystring, the CAPTCHA object from the Cache, and renders the CAPTCHA image. It then removes the Cache object.
Note that there is a little cleanup involved at the end. If, for some reason, the control renders but the image URL is never retrieved, there would be an orphan CAPTCHA object in the Cache. This can happen, but should be rare in practice-- and our Cache entry only has a 20 minute lifetime anyway.
One mistake I made early on was storing the actual CAPTCHA text in the ViewState. The ViewState is not encrypted and can be easily decoded! I've switched to ControlState for the GUID, which is essential for retrieving the shared Captcha control from the Cache -- but by itself, it is useless.
CaptchaControl Properties
The CaptchaControl is a good ASP.NET citizen, and properly implements all the default ASP.NET Server Control properties. It also has a few properties of its own:

| Property |
Default |
Description |
CacheStrategy |
HttpRuntime |
For security reasons, the CAPTCHA text is never sent to the client; it is only stored on the server. It can be stored in Session (web-farm friendly) or HttpRuntime (very fast, but local to one webserver). |
CaptchaBackgroundNoise |
Low |
Amount of background noise to add to the CAPTCHA image. Ranges from None to Extreme. |
CaptchaChars |
A-Z, 1-9 |
A whitelist of characters to use when building CAPTCHA text. A character will be picked randomly from this string. By default, I omit some characters likely to be confused, such as O, 0, I, 1, 8, B, etcetera. |
CaptchaFont |
"" |
Font family to use for the CAPTCHA text. If not provided, a random installed font will be chosen for each character. A font whitelist is maintained internally so only known legible fonts will be used (e.g., not WingDings). |
CaptchaFontWarping |
Low |
Level of warping used on each character of the CAPTCHA text. Ranges from None to Extreme. |
CaptchaHeight |
50 |
Default height of the CAPTCHA image, in pixels. |
CaptchaLength |
5 |
Number of characters used in the randomly generated CAPTCHA text. |
CaptchaLineNoise |
None |
Amount of "scribble" line noise to add to the CAPTCHA image. Ranges from None to Extreme. |
CaptchaMaxTimeout |
90 |
Number of seconds that the CAPTCHA will remain valid and stored in the cache after it is generated. |
CaptchaMinTimeout |
3 |
Minimum number of seconds the user must wait before entering a CAPTCHA. |
CaptchaWidth |
180 |
Default width of the CAPTCHA image, in pixels. |
UserValidated |
False |
After postback, returns True if the user entered text that matches the randomly generated CAPTCHA text. Note that the standard IValidation interface is implemented as well. |
LayoutStyle |
Horizontal |
Determines if the text and input box are to the right, or below, the image. Allows greater layout flexibility. |
Many of these properties have to do with the inherent tradeoff between human readability and machine readability. The harder a CAPTCHA is for OCR software to read, the harder it will be for us human beings, too! For illustration, compare these two CAPTCHA images:

The CAPTCHA on the left is generated with all "medium" settings, which are a reasonable tradeoff between human readability and OCR machine readability. The CAPTCHA on the right uses a lower CaptchaFontWarping, and a smaller CaptchaLength. If the risk of someone writing OCR scripts to defeat your CAPTCHA is low, I strongly urge you to use the easier-to-read CAPTCHA settings. Remember, just having a CAPTCHA at all raises the bar quite high.
The CaptchaTimeout property was added later to alleviate concerns about CAPTCHA farming. It is possible to "pay" humans to solve harvested CAPTCHAs by re-displaying a CAPTCHA and giving the user free MP3s or access to pornography if they solve it. However, this technique takes time, and it doesn't work if the CAPTCHA has a time-limited expiration.
Conclusion
Many thanks to BrainJar for creating his simple yet effective CAPTCHA image class. Now that I've wrapped it up into an ASP.NET server control, it should be easier than ever to simply drop on a web form, set a few properties, and start defeating spammers at their own game!
There are many more details and comments in the demonstration solution provided at the top of the article, so check it out. And please don't hesitate to provide feedback, good or bad! I hope you enjoyed this article. If you did, you may also like my other articles as well.
History
- Monday, November 8, 2004 - Published.
- Friday, December 17, 2004 - Version 1.1
- added
UserValidationEvent
- changed defaults to be less aggressive (more user friendly)
- added
LayoutStyle property for choice of horizontal or vertical layout
- changed random font approach from blacklist to whitelist
- corrected intermittent order-of-retrieval bug reported by Robert Sindall
- converted to VB.NET 2005 compatible XML comments
- Sunday, October 29, 2006 - Version 2.0
- major rewrite for .NET 2.0
- removed dependency on
Session
- removed dependency on
ViewState
- uses
ControlState to store GUID
- implemented standard
IValidator
- complete rewrite of the renderer for more secure CAPTCHAs
- added more tweakable properties
- switched to
HttpRuntime caching
- changed cache priority to
Caching.CacheItemPriority.NotRemovable (this was a bug, fixed in the old and new versions)
- Monday, January 29, 2007 - Version 2.1
- Correct length bug
- Correct caching bug (units were set to minutes, not seconds!)
- Add option to store CAPTCHA text in
Session for web farms
- Add minimum time to prevent aggressive robots
- Improved response messages to display exactly why the CAPTCHA was rejected (timed out, bad entry, too fast)
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 212 (Total in Forum: 212) (Refresh) | FirstPrevNext |
|
|
 |
|
|
I compiled it, added it to my toolkit, dropped it onto a page and works GREAT! Problem is, my form has other input fields it needs to validate. These other fields (e.g., first name, last name, email) have requiredfieldvalidators that seem to be overriding any error messaging coming from the CATPCHA control, and therefore, the CAPTCHA error messages don't show up in the validation summary any more.
Any thoughts?
Thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I tried out Yeroon's "HowTo: Implement validation for this control on your WebForm (now with proper message formatting)" fix and this worked.
I'd suggest it for anyone else having similar issues.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
hi .
please tell me how to validate this control . its user validity property is readonly and how we to stop post back because first time on submit click it goes to vlaidate and ween second time clink on submit then its validated .
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Is there a way I can programmatically set the focus to the control's textbox? I'd like to do it in one of two ways: 1) Set the tab index so that the focus is set when exiting the previous control OR 2) Using JavaScript, set the focus after X characters have been keyed in the previous control (I already have the JS for this, but need to be able to identify the control's textbox somehow)
Thanks, Mike
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Does anyone have a C# version, with step-by-step (PLEASE) installation instructions?? tx, matt
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
The source file download is causing IE to crash! This might be due to the files not being there!!
What's Up!!!???!!!???!!!???
The man in black ran across the desert....and the gunslinger followed
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Right...clicking "open" instead of "save" worked. Very weird!
The man in black ran across the desert....and the gunslinger followed
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello, I found a candidate bug for a next release In order to enable SessionState mode of the CAPTCHA control you need to implement at least IRequiresSessionState in your CaptchaImageHandler. You can find more info in this post
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Nice cool control, but not working on web farm, and I mean when a web have more than 1 thread.
I have made all you say, and all other say but still not work
-- after some work --
the solution is to place it on the app instanse, so get it with this
ci = (CaptchaImage)HttpContext.Current.ApplicationInstance.Application.Get(guid);
and place it like this HttpContext.Current.ApplicationInstance.Application.Add(_captcha.UniqueId, _captcha);
I check it on my site, and this way is work, no session
-- after more work and test-- nether the ApplicationInstance working correct on multi web farm with multi threads. ....
modified on Wednesday, March 5, 2008 3:30 AM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
well this is final, this control is not working in a real web enviroment,
but nice try, and nice captcha image, nice ideas, nice example,
I get this code, and some other code form codeproject with image captcha and made a new one that work.
thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
My web application use form authentication I place the CAPTCHA Server Control in the login page, it can't display the image. But if I place the CAPTCHA Server Control in other page, it display well.
Is it not suitable for form authentication? or How can I do?
|
| Sign In·View Thread·PermaLink | 3.00/5 (2 votes) |
|
|
|
 |
|
|
 |
|
|
 |
|
|
First, sorry for some change to CHS... but it work...
CaptchaControl.cs
using System; using System.Collections.Generic; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Drawing; using System.Collections; using System.Collections.Specialized; using System.ComponentModel;
namespace WebControlCaptcha_for_C_sharp { public class CaptchaControl : System.Web.UI.WebControls.WebControl, INamingContainer, IPostBackDataHandler, IValidator { public enum Layout { Horizontal, Vertical, };
public enum CacheType { HttpRuntime, Session, };
private int _timeoutSecondsMax = 90; private int _timeoutSecondsMin = 3; private bool _userValidated = true; private String _text = "Enter the code shown:"; private String _font = ""; private CaptchaImage _captcha = new CaptchaImage(); private Layout _layoutStyle = Layout.Horizontal; private String _prevguid; private String _errorMessage = ""; private CacheType _cacheStrategy = CacheType.HttpRuntime;
#region ///Public Properties
[Bindable(true), Category("Appearance"), DefaultValue("The text you typed does not match the text in the image."), Description("Message to display in a Validation Summary when the CAPTCHA fails to validate.")] public String ErrorMessage { get { if( ! _userValidated ) return _errorMessage; else return ""; }
set { _errorMessage = value; } }
[Browsable(false), Category("Behavior"), DefaultValue(true), Description("Is Valid"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsValid { get { return _userValidated; } set { } }
// // // public override bool Enabled { get { return base.Enabled; } set { base.Enabled = value; // When a validator is disabled, generally, the intent is not to // make the page invalid for that round trip. if( ! value ) _userValidated = true; } }
[DefaultValue("Enter the code shown above:"), Description("Instructional text displayed next to CAPTCHA image."), Category("Appearance")] public String Text { get { return _text; } set { _text = value; } }
[DefaultValue( CaptchaControl.Layout.Horizontal ), Description("Determines if image and input area are displayed horizontally, or vertically."), Category("Captcha")] public Layout LayoutStyle { get { return _layoutStyle; } set { _layoutStyle = value; } }
[DefaultValue( CaptchaControl.CacheType.HttpRuntime ), Description("Determines if CAPTCHA codes are stored in HttpRuntime (fast, but local to current server) or Session (more portable across web farms)."), Category("Captcha")] public CacheType CacheStrategy { get { return _cacheStrategy; } set { _cacheStrategy = value; } }
[Description("Returns True if the user was CAPTCHA validated after a postback."), Category("Captcha")] public bool UserValidated { get { return _userValidated; } }
[DefaultValue(""), Description("Font used to render CAPTCHA text. If font name is blank, a random font will be chosen."), Category("Captcha")] public String CaptchaFont { get { return _font; } set { _font = value; _captcha.Font = _font; } }
[DefaultValue(""), Description("Characters used to render CAPTCHA text. A character will be picked randomly from the string."), Category("Captcha")] public String CaptchaChars { get { return _captcha.TextChars; }
set { _captcha.TextChars = value; } }
[DefaultValue(5), Description("Number of CaptchaChars used in the CAPTCHA text"), Category("Captcha")] public int CaptchaLength { get { return _captcha.TextLength; }
set { _captcha.TextLength = value; } }
[DefaultValue(2), Description("Minimum number of seconds CAPTCHA must be displayed before it is valid. If you're too fast, you must be a robot. Set to zero to disable."), Category("Captcha")] public int CaptchaMinTimeout { get { return _timeoutSecondsMin; } set { if (value > 15) throw new ArgumentOutOfRangeException("CaptchaTimeout", "Timeout must be less than 15 seconds. Humans aren't that slow!"); _timeoutSecondsMin = value; } }
[DefaultValue(90), Description("Maximum number of seconds CAPTCHA will be cached and valid. If you're too slow, you may be a CAPTCHA hack attempt. Set to zero to disable."), Category("Captcha")] public int CaptchaMaxTimeout { get { return _timeoutSecondsMax; } set { if( value < 15 && value != 0 ) throw new ArgumentOutOfRangeException("CaptchaTimeout", "Timeout must be greater than 15 seconds. Humans can't type that fast!"); _timeoutSecondsMax = value; } }
[DefaultValue(50), Description("Height of generated CAPTCHA image."), Category("Captcha")] public int CaptchaHeight { get { return _captcha.Height; }
set { _captcha.Height = value; } }
[DefaultValue(180), Description("Width of generated CAPTCHA image."), Category("Captcha")] public int CaptchaWidth { get { return _captcha.Width; }
set { _captcha.Width = value; } }
[DefaultValue(CaptchaImage.FontWarpFactor.Low), Description("Amount of random font warping used on the CAPTCHA text"), Category("Captcha")] public CaptchaImage.FontWarpFactor CaptchaFontWarping { get { return _captcha.FontWarp; }
set { _captcha.FontWarp = value; } }
[DefaultValue( CaptchaImage.BackgroundNoiseLevel.Low ), Description("Amount of background noise to generate in the CAPTCHA image"), Category("Captcha")] public CaptchaImage.BackgroundNoiseLevel CaptchaBackgroundNoise { get { return _captcha.BackgroundNoise; } set { _captcha.BackgroundNoise = value; } }
[DefaultValue( CaptchaImage.LineNoiseLevel.None ), Description("Add line noise to the CAPTCHA image"), Category("Captcha")] public CaptchaImage.LineNoiseLevel CaptchaLineNoise { get { return _captcha.LineNoise; } set { _captcha.LineNoise = value; } }
#endregion
public void Validate() { //-- a no-op, since we validate in LoadPostData }
private CaptchaImage GetCachedCaptcha( String guid ) { if( _cacheStrategy == CacheType.HttpRuntime ) return (CaptchaImage)HttpRuntime.Cache.Get(guid) ; else return (CaptchaImage)HttpContext.Current.Session[guid]; }
private void RemoveCachedCaptcha( String guid ) { if (_cacheStrategy == CacheType.HttpRuntime) HttpRuntime.Cache.Remove(guid); else HttpContext.Current.Session.Remove(guid); }
/// /// are we in design mode? /// private bool IsDesignMode { get { return HttpContext.Current == null; } }
/// /// Validate the user's text against the CAPTCHA text /// private void ValidateCaptcha( String userEntry ) { if( !Visible || !Enabled ) { _userValidated = true; return; }
//-- retrieve the previous captcha from the cache to inspect its properties CaptchaImage ci = GetCachedCaptcha(_prevguid); if( ci == null ) { //this.ErrorMessage = "The code you typed has expired after " + this.CaptchaMaxTimeout + " seconds."; this.ErrorMessage = "??????????" + this.CaptchaMaxTimeout + "?????"; _userValidated = false; return; }
//-- was it entered too quickly? if( this.CaptchaMinTimeout > 0 ) { if( ci.RenderedAt.AddSeconds( this.CaptchaMinTimeout ) > DateTime.Now ) { _userValidated = false; //this.ErrorMessage = "Code was typed too quickly. Wait at least " + this.CaptchaMinTimeout + " seconds."; this.ErrorMessage = "?????????????" + this.CaptchaMinTimeout + "??????"; RemoveCachedCaptcha(_prevguid); return; } }
if( String.Compare(userEntry, ci.Text, true) != 0 ) { //this.ErrorMessage = "The code you typed does not match the code in the image."; this.ErrorMessage = "????????????????"; _userValidated = false; RemoveCachedCaptcha(_prevguid); return; }
_userValidated = true; RemoveCachedCaptcha(_prevguid); }
/// /// returns HTML-ized color strings /// private String HtmlColor( Color color ) { if( color.IsEmpty ) return ""; if( color.IsNamedColor ) return color.ToKnownColor().ToString(); if( color.IsSystemColor ) return color.ToString();
return "#" + color.ToArgb().ToString("x").Substring(2); }
/// /// returns css "style=" tag for this control /// based on standard control visual properties /// private String CssStyle() { System.Text.StringBuilder sb = new System.Text.StringBuilder(); String strColor;
sb.Append(" style='");
if( BorderWidth.ToString().Length > 0 ) { sb.Append("border-width:"); sb.Append(BorderWidth.ToString()); sb.Append(";"); }
if( BorderStyle != System.Web.UI.WebControls.BorderStyle.NotSet ) { sb.Append("border-style:"); sb.Append(BorderStyle.ToString()); sb.Append(";"); }
strColor = HtmlColor(BorderColor); if( strColor.Length > 0 ) { sb.Append("border-color:"); sb.Append(strColor); sb.Append(";"); }
strColor = HtmlColor(BackColor); if( strColor.Length > 0 ) sb.Append("background-color:" + strColor + ";");
strColor = HtmlColor(ForeColor); if( strColor.Length > 0 ) sb.Append("color:" + strColor + ";");
if( Font.Bold ) sb.Append("font-weight:bold;");
if( Font.Italic ) sb.Append("font-style:italic;");
if( Font.Underline ) sb.Append("text-decoration:underline;"); if( Font.Strikeout ) sb.Append("text-decoration:line-through;");
if( Font.Overline ) sb.Append("text-decoration:overline;");
if( Font.Size.ToString().Length > 0 ) sb.Append("font-size:" + Font.Size.ToString() + ";");
if( Font.Names.Length > 0 ) { String strFontFamily; sb.Append("font-family:"); int i=0; for (strFontFamily = Font.Names[i]; i < Font.Names.Length; i++) { sb.Append(strFontFamily); sb.Append(","); } sb.Length = sb.Length - 1; sb.Append(";"); }
if( Height.ToString() != "" ) sb.Append("height:" + Height.ToString() + ";"); if( Width.ToString() != "" ) sb.Append("width:" + Width.ToString() + ";");
sb.Append("'");
if( sb.ToString() == " style=''" ) return ""; else return sb.ToString(); }
/// /// render raw control HTML to the page /// protected override void Render( HtmlTextWriter Output ) { Output.Write(" if( CssClass != "" ) Output.Write(" class='" + CssClass + "'"); Output.Write( CssStyle() ); Output.Write(">");
//-- image DIV/SPAN if( this.LayoutStyle == Layout.Vertical ) Output.Write(""); else Output.Write(""); //-- this is the URL that triggers the CaptchaImageHandler Output.Write("<img src="\"CaptchaImage.aspx");<br" mode="hold" /> if( ! IsDesignMode ) Output.Write("?guid=" + Convert.ToString(_captcha.UniqueId)); Output.Write("\" border='0'"); if( ToolTip.Length > 0 ) Output.Write(" alt='" + ToolTip + "'"); Output.Write(" width=" + _captcha.Width); Output.Write(" height=" + _captcha.Height); Output.Write(">"); if( this.LayoutStyle == Layout.Vertical ) Output.Write(" "); else Output.Write("");
//-- text input and submit button DIV/SPAN if( this.LayoutStyle == Layout.Vertical ) Output.Write(""); else Output.Write(""); if( _text.Length > 0 ) { Output.Write(_text); Output.Write(" "); } Output.Write("<input name=" + UniqueID + " type="text" size=");<br mode=" hold=" /"> Output.Write(_captcha.TextLength.ToString() ); Output.Write(" maxlength="); Output.Write(_captcha.TextLength.ToString() ); if( AccessKey.Length > 0 ) Output.Write(" accesskey=" + AccessKey); if( ! Enabled ) Output.Write(" disabled=\"disabled\""); if( TabIndex > 0 ) Output.Write(" tabindex=" + TabIndex.ToString());
Output.Write(" value=''>"); if( this.LayoutStyle == Layout.Vertical ) Output.Write("</input> "); else { Output.Write(""); Output.Write(" "); }
//-- closing tag for master DIV Output.Write(""); }
/// /// generate a new captcha and store it in the ASP.NET Cache by unique GUID /// private void GenerateNewCaptcha() { if( ! IsDesignMode ) { if( _cacheStrategy == CacheType.HttpRuntime ) { HttpRuntime.Cache.Add( _captcha.UniqueId, _captcha, null, DateTime.Now.AddSeconds( Convert.ToDouble( (this.CaptchaMaxTimeout == 0 ? 90 : this.CaptchaMaxTimeout) ) ), TimeSpan.Zero, System.Web.Caching.CacheItemPriority.NotRemovable, null ); } else HttpContext.Current.Session.Add(_captcha.UniqueId, _captcha); } }
/// /// Retrieve the user's CAPTCHA input from the posted data /// public bool LoadPostData( String PostDataKey, NameValueCollection Values ) { ValidateCaptcha(Convert.ToString(Values[this.UniqueID])); return false; }
// just void public void RaisePostDataChangedEvent() { }
protected override Object SaveControlState() { return (Object)_captcha.UniqueId; }
protected override void LoadControlState( Object state ) { if( state != null ) _prevguid = (String)state; }
protected override void OnInit( System.EventArgs e ) { base.OnInit(e); Page.RegisterRequiresControlState(this); Page.Validators.Add(this); }
protected override void OnUnload( System.EventArgs e ) { if (Page != null) Page.Validators.Remove(this); base.OnUnload(e); }
protected override void OnPreRender( System.EventArgs e ) { if( this.Visible ) GenerateNewCaptcha(); base.OnPreRender(e); } } }
CaptchaImage.cs
using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging;
namespace WebControlCaptcha_for_C_sharp { public class CaptchaImage { private int _height; private int _width; private Random _rand; private DateTime _generatedAt; private String _randomText; private int _randomTextLength; private String _randomTextChars; private String _fontFamilyName; private FontWarpFactor _fontWarp; private BackgroundNoiseLevel _backgroundNoise; private LineNoiseLevel _lineNoise; private String _guid; private String _fontWhitelist;
#region Public?? /// /// Amount of random font warping to apply to rendered text /// public enum FontWarpFactor { None, Low, Medium, High, Extreme, }
/// /// Amount of background noise to add to rendered image /// public enum BackgroundNoiseLevel { None, Low, Medium, High, Extreme, }
/// /// Amount of curved line noise to add to rendered image /// public enum LineNoiseLevel { None, Low, Medium, High, Extreme, } #endregion
#region Public Properties
/// /// Returns a GUID that uniquely identifies this Captcha /// public String UniqueId { get { return _guid; } }
/// /// Returns the date and time this image was last rendered /// public DateTime RenderedAt { get { return _generatedAt; } }
/// /// Font family to use when drawing the Captcha text. If no font is provided, a random font will be chosen from the font whitelist for each character. /// public String Font { get { return _fontFamilyName; }
set { try { Font font1 = new Font(value, 12.0f); _fontFamilyName = value; font1.Dispose(); } catch { _fontFamilyName = System.Drawing.FontFamily.GenericSerif.Name; } } }
/// /// Amount of random warping to apply to the Captcha text. /// public FontWarpFactor FontWarp { get { return _fontWarp; } set { _fontWarp = value; } }
/// /// Amount of background noise to apply to the Captcha image. /// public BackgroundNoiseLevel BackgroundNoise { get { return _backgroundNoise; } set { _backgroundNoise = value; } }
public LineNoiseLevel LineNoise { get { return _lineNoise; } set { _lineNoise = value; } }
/// /// A string of valid characters to use in the Captcha text. /// A random character will be selected from this string for each character. /// public String TextChars { get { return _randomTextChars; } set { _randomTextChars = value; _randomText = GenerateRandomText(); } }
/// /// Number of characters to use in the Captcha text. /// public int TextLength { get { return _randomTextLength; } set { _randomTextLength = value; _randomText = GenerateRandomText(); } }
/// /// Returns the randomly generated Captcha text. /// public String Text { get { return _randomText; } }
/// /// Width of Captcha image to generate, in pixels /// public int Width { get { return _width; } set { if( value <= 60 ) throw new ArgumentOutOfRangeException("width", value, "width must be greater than 60."); _width = value; } }
/// /// Height of Captcha image to generate, in pixels /// public int Height { get { return _height; } set { if( value <= 30 ) throw new ArgumentOutOfRangeException("height", value, "height must be greater than 30."); _height = value; } }
/// /// A semicolon-delimited list of valid fonts to use when no font is provided. /// public String FontWhitelist { get { return _fontWhitelist; } set { _fontWhitelist = value; } }
#endregion
public CaptchaImage() { _rand = new Random(); _fontWarp = FontWarpFactor.Low; _backgroundNoise = BackgroundNoiseLevel.Low; _lineNoise = LineNoiseLevel.None; _width = 180; _height = 50; _randomTextLength = 5; _randomTextChars = "ACDEFGHJKLNPQRTUVXYZ2346789"; _fontFamilyName = ""; // -- a list of known good fonts in on both Windows XP and Windows Server 2003 _fontWhitelist = "arial;arial black;comic sans ms;courier new;estrangelo edessa;franklin gothic medium;" + "georgia;lucida console;lucida sans unicode;mangal;microsoft sans serif;palatino linotype;" + "sylfaen;tahoma;times new roman;trebuchet ms;verdana"; _randomText = GenerateRandomText(); _generatedAt = DateTime.Now; _guid = Guid.NewGuid().ToString(); }
/// /// Forces a new Captcha image to be generated using current property value settings. /// public Bitmap RenderImage() { return GenerateImagePrivate(); }
// private static String[] ff; /// /// Returns a random font family from the font whitelist /// private String RandomFontFamily() { //-- small optimization so we don't have to split for each char if( ff == null ) { char[] charSeparators = new char[] { ';' }; ff = _fontWhitelist.Split( charSeparators, StringSplitOptions.RemoveEmptyEntries ); }
return ff[_rand.Next(0, ff.Length)]; }
/// /// generate random text for the CAPTCHA /// private String GenerateRandomText() { System.Text.StringBuilder sb = new System.Text.StringBuilder(_randomTextLength);
int maxLength = _randomTextChars.Length;
for( int n = 0; n < _randomTextLength; n++ ) sb.Append(_randomTextChars.Substring(_rand.Next(maxLength), 1));
return sb.ToString(); }
/// /// Returns a random point within the specified x and y ranges /// private PointF RandomPoint( int xmin, int xmax, int ymin, int ymax ) { return new PointF(_rand.Next(xmin, xmax), _rand.Next(ymin, ymax)); }
/// /// Returns a random point within the specified rectangle /// private PointF RandomPoint( Rectangle rect ) { return RandomPoint(rect.Left, rect.Width, rect.Top, rect.Bottom); } /// /// Returns a GraphicsPath containing the specified string and font /// private GraphicsPath TextPath( String s, Font f, Rectangle r ) { StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Near; sf.LineAlignment = StringAlignment.Near; GraphicsPath gp = new GraphicsPath(); gp.AddString(s, f.FontFamily, (int)f.Style, f.Size, r, sf); return gp; }
/// /// Returns the CAPTCHA font in an appropriate size /// private Font GetFont() { float fsize = 0; String fname = _fontFamilyName;
if( fname == "" ) { fname = RandomFontFamily(); } switch( this.FontWarp ) { case FontWarpFactor.None: fsize = Convert.ToInt32(_height * 0.7); break; case FontWarpFactor.Low: fsize = Convert.ToInt32(_height * 0.8); break; case FontWarpFactor.Medium: fsize = Convert.ToInt32(_height * 0.85); break; case FontWarpFactor.High: fsize = Convert.ToInt32(_height * 0.9); break; case FontWarpFactor.Extreme: fsize = Convert.ToInt32(_height * 0.95); break; }
return new Font(fname, fsize, FontStyle.Bold); }
/// /// Renders the CAPTCHA image /// private Bitmap GenerateImagePrivate() { Font fnt = null; Rectangle rect; Brush br; Bitmap bmp = new Bitmap( _width, _height, PixelFormat.Format32bppArgb ); Graphics gr = Graphics.FromImage( bmp ); gr.SmoothingMode = SmoothingMode.AntiAlias;
// -- fill an empty white rectangle rect = new Rectangle(0, 0, _width, _height); br = new SolidBrush(Color.White); gr.FillRectangle(br, rect);
int charOffset = 0; double charWidth = _width / _randomTextLength; Rectangle rectChar;
foreach( char c in _randomText ) { //-- establish font and draw area fnt = GetFont(); rectChar = new Rectangle(Convert.ToInt32(charOffset * charWidth), 0, Convert.ToInt32(charWidth), _height);
//-- warp the character GraphicsPath gp = TextPath(c.ToString(), fnt, rectChar); WarpText(gp, rectChar);
//-- draw the character br = new SolidBrush(Color.Black); gr.FillPath(br, gp);
charOffset += 1; }
AddNoise(gr, rect); AddLine(gr, rect);
//-- clean up unmanaged resources fnt.Dispose(); br.Dispose(); gr.Dispose();
return bmp; }
/// /// Warp the provided text GraphicsPath by a variable amount /// private void WarpText( GraphicsPath textPath, Rectangle rect ) { float WarpDivisor = 0; float RangeModifier = 0;
switch( _fontWarp ) { case FontWarpFactor.None: return; case FontWarpFactor.Low: WarpDivisor = 6.0f; RangeModifier = 1.0f; break; case FontWarpFactor.Medium: WarpDivisor = 5.0f; RangeModifier = 1.3f; break; case Fo | | | | | |