Click here to Skip to main content
15,885,960 members
Articles / Web Development / ASP.NET

ASP.NET Controls – CommunityServer Captcha ControlAdapter, a practical case

Rate me:
Please Sign up or sign in to vote.
4.60/5 (3 votes)
13 Apr 2010CPOL4 min read 9.5K   4  
The ControlAdapter is available since .NET framework version 2.0 and his main goal is to adapt and customize a control render in order to achieve a specific behavior or layout.

The ControlAdapter is available since .NET framework version 2.0 and his main goal is to adapt and customize a control render in order to achieve a specific behavior or layout. This customization is done without changing the base control.

A ControlAdapter is commonly used to custom render for specific platforms like Mobile.

In this particular case the ControlAdapter was used to add a specific behavior to a Control. In this  post I will use one adapter to add a Captcha to all WeblogPostCommentForm controls within pontonetpt.com CommunityServer instance.

The Challenge

The ControlAdapter complexity is usually associated with the complexity/structure of is base control. This case is precisely one of those since base control dynamically load his content (controls) thru several ITemplate.

Those of you who already played with ITemplate knows that while it is an excellent option for control composition it also brings to the table a big issue:

“Controls defined within a template are not available for manipulation until they are instantiated inside another control.”

While analyzing the WeblogPostCommentForm control I found that he uses the ITemplate technique to compose it’s layout and unfortunately I also found that the template content vary from theme to theme. This could have been a problem but luckily WeblogPostCommentForm control template content always contains a submit button with a well known ID (at least I can assume that there are a well known set of IDs).

Using this submit button as anchor it’s possible to add the Captcha controls in the correct place.

Another important finding was that WeblogPostCommentForm control inherits from the WrappedFormBase control which is the base control for all CommunityServer input forms.

Knowing this inheritance link the main goal has changed to became the creation of a base ControlAdapter that  could be extended and customized to allow adding Captcha to:

  • post comments form
  • contact form
  • user creation form.

And, with this mind set, I decided to used the following ControlAdapter base class signature :

public abstract class WrappedFormBaseCaptchaAdapter<T> : ControlAdapter where T : WrappedFormBase{}
Great, but there are still many to do …

Captcha

The Captcha will be assembled with:

  • A dynamically generated image with a set of random numbers
  • A TextBox control where the image number will be inserted
  • A Validator control to validate whether TextBox numbers match the image numbers

This is a common Captcha implementation, is not rocket science and don’t bring any additional problem. The main problem, as told before, is to find the correct anchor control to ensure a correct Captcha control injection.

The anchor control can vary by:

  • target control 
  • theme

Implementation

To support this dynamic scenario I choose to use the following implementation:

private List<string> _validAnchorIds = null;protected virtual List<string> ValidAnchorIds{    get    {        if (this._validAnchorIds == null)        {            this._validAnchorIds = new List<string>();            this._validAnchorIds.Add("btnSubmit");        }        return this._validAnchorIds;    }}private Control GetAnchorControl(T wrapper){    if (this.ValidAnchorIds == null || this.ValidAnchorIds.Count == 0)    {        throw new ArgumentException("Cannot be null or empty", "validAnchorNames");    }    var q = from anchorId in this.ValidAnchorIds            let anchorControl = CSControlUtility.Instance().FindControl(wrapper, anchorId)            where anchorControl != null            select anchorControl;    return q.FirstOrDefault();}

I can now, using the ValidAnchorIds property, configure a set of valid anchor control  Ids.

The GetAnchorControl method searches for a valid anchor control within the set of valid control Ids. Here, some of you may question why to use a LINQ To Objects expression, but the important here is to notice the usage of CSControlUtility.Instance().FindControl CommunityServer method. I want to build on top of CommunityServer not to reinvent the wheel.

Assuming that an anchor control was found, it’s now possible to inject the Captcha at the correct place. This not something new, we do this all the time when creating server controls or adding dynamic controls:

protected sealed override void CreateChildControls(){    base.CreateChildControls();    if (this.IsCaptchaRequired)    {        T wrapper = base.Control as T;        if (wrapper != null)        {            Control anchorControl = GetAnchorControl(wrapper);            if (anchorControl != null)            {                Panel phCaptcha = new Panel {CssClass = "CommonFormField", ID = "Captcha"};                int index = anchorControl.Parent.Controls.IndexOf(anchorControl);                anchorControl.Parent.Controls.AddAt(index, phCaptcha);                CaptchaConfiguration.DefaultProvider.AddCaptchaControls(                    phCaptcha,                    GetValidationGroup(wrapper, anchorControl));            }        }    }}

Here you can see a new entity in action: a provider. This is a CaptchaProvider class instance and is only goal is to create the Captcha itself and do everything else is needed to ensure is correct operation.

public abstract class CaptchaProvider : ProviderBase{    public abstract void AddCaptchaControls(Panel captchaPanel, string validationGroup);}

You can create your own specific CaptchaProvider class to use different Captcha strategies including the use of existing Captcha services  like ReCaptcha.

Once the generic ControlAdapter was created became extremely easy to created a specific one. Here is the specific ControlAdapter for the WeblogPostCommentForm control:

public class WeblogPostCommentFormCaptchaAdapter : WrappedFormBaseCaptchaAdapter<WrappedFormBase>{    #region Overriden Methods    protected override List<string> ValidAnchorIds    {        get        {            List<string> validAnchorNames = base.ValidAnchorIds;            validAnchorNames.Add("CommentSubmit");            return validAnchorNames;        }    }    protected override string DefaultValidationGroup    {        get { return "CreateCommentForm"; }    }    #endregion Overriden Methods}

Configuration

This is the magic step.

Without changing the original pages and keeping the application original assemblies untouched we are going to add a new behavior to the CommunityServer application.

To glue everything together you must follow this steps:

  1. Add the following configuration to default.browser file:
    <?xml version='1.0' encoding='utf-8'?><browsers>  <browser refID="Default">    <controlAdapters>      <!-- Adapter for the WeblogPostCommentForm control in order to add the Captcha and prevent SPAM comments -->      <adapter controlType="CommunityServer.Blogs.Controls.WeblogPostCommentForm" adapterType="NunoGomes.CommunityServer.Components.WeblogPostCommentFormCaptchaAdapter, NunoGomes.CommunityServer" />    </controlAdapters>  </browser></browsers>
    
  2. Add the following configuration to web.config file:
    <configuration>  <configSections>    <!-- New section for Captcha providers configuration -->    <section name="communityServer.Captcha" type="NunoGomes.CommunityServer.Captcha.Configuration.CaptchaSection" />  </configSections>
    
    <!-- Configuring a simple Captcha provider -->  <communityServer.Captcha defaultProvider="simpleCaptcha">    <providers>      <add name="simpleCaptcha" type="NunoGomes.CommunityServer.Captcha.Providers.SimpleCaptchaProvider, NunoGomes.CommunityServer"            imageUrl="~/captcha.ashx"            enabled="true"            passPhrase="_YourPassPhrase_"            saltValue="_YourSaltValue_"            hashAlgorithm="SHA1"            passwordIterations="3"            keySize="256"            initVector="_YourInitVectorWithExactly_16_Bytes_"            />    </providers>  </communityServer.Captcha>
    
    <system.web>    <httpHandlers>      <!-- The Captcha Image handler used by the simple Captcha provider -->      <add verb="GET" path="captcha.ashx" type="NunoGomes.CommunityServer.Captcha.Providers.SimpleCaptchaProviderImageHandler, NunoGomes.CommunityServer" />    </httpHandlers>  </system.web>  <system.webServer>    <handlers accessPolicy="Read, Write, Script, Execute">      <!-- The Captcha Image handler used by the simple Captcha provider -->      <add verb="GET" name="captcha" path="captcha.ashx" type="NunoGomes.CommunityServer.Captcha.Providers.SimpleCaptchaProviderImageHandler, NunoGomes.CommunityServer" />    </handlers>  </system.webServer></configuration>
    

Conclusion

Building a ControlAdapter can be complex but the reward is his ability to allows us, thru configuration changes, to modify an application render and/or behavior.

You can see this ControlAdapter in action here and here (anonymous required).

A complete solution is available in “CommunityServer Extensions” Codeplex project.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect everis
Portugal Portugal
Over 13 years of experience in the Software Development working mainly in the banking and insurance industry.

Over 3 year of experience as Operations Team Leader focused on Infrastructure Management and Software Configuration Management.

I've been honored with the Microsoft Most Valuable Professional (MVP) Award for three consecutive years, 2010, 2011 and 2012, in recognition to exceptional technical contributions and leadership.

Current / Recent Technical Projects
- Dominican Republic Instance management, including 2nd line System management, capacity management, SW monitoring and deploy management
- Colombian SECOPII Instance management, including 2nd line System management, capacity management, SW monitoring and deploy management
- Vortal Main Instance management, including 2nd line System management, capacity management, SW monitoring and deploy management
- Vortal Development ecosystem management, including Server management, , capacity management, SW monitoring and deploy management

Areas of Specialization:
- Operations Management - ISO 20000 & ISO 27001 driven
- Team Management and Coaching
- Technology Leadership, Solutions/Architecture
- Product life cycle management, Continuous Integration
- Technological background in Microsoft frameworks and tools.

Comments and Discussions

 
-- There are no messages in this forum --