ELWEA - EventLog WebEvent Aggregator for ASP.NET

by nmgomes

Last time I wrote about Health Monitoring was during 2008 and since then I ‘m using, on a daily basis, a complex Health Monitoring EventLog provider to aggregate WebEvents from specific ASP.NET web applications.

Last year I found ELMAH (Error Logging Module and Handler) project and became a fan of the concept:

“[…] facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.”

It didn’t take much time until I start thinking  about releasing a version of my EventLog WebEvents aggregator.

I’m now happy to announce that I finally made this project available at Codeplex and name it: ELWEA - EventLog WebEvent Aggregator for ASP.NET.

While different in nature this project main goal is pretty similar to the ELMAH project: keep an ASP.NET web application error tracking isolated and easily accessible.

What makes this project different from many other like ELMAH is the fact that it's not focused on handling and persisting error info. This project main goal is to aggregate all EventLog data belong to a specific web application under a specific EventLog source name.



I’m sure that web developer and web site administrators will love it but I think that also IT pros that manage shared hosting environments will use it.

Visit here for a more complete description and here for the project documentation.

To make your live even simpler I’m planning to make a NuGet package available for ELWEA. Keep an eye on this.

Enjoy it!

Filed in: ASP.NET | CodeProject | .NET


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

by nmgomes

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 …


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


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}


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>


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.


ASP.NET Controls – Problem sorting GridView with SqlDataSource control

by nmgomes

Let me start by saying that Microsoft don't consider this issue as a problem, as you can see here this is a “by design” behavior.

The problem is well described in the referred Connect feedback and it contains a workaround.

Although simple, the workaround requires you to always register the GridView Sorting event and make the tweak according to the current GridView settings. Well, if are like me you will forget to do it half the times needed.

So, I made a not so simple workaround that will take care of the issue for me.

I override the OnSorting method from GridView so I can handle the GridViewEventArgs instance and override its SortDirection value.

To turn this into a general solution I partially reproduce the ParseSortString method from DataTable to find out if the current SortExpression contains either the ASC or DESC keywords.

Here is the code:

public class GridView : global::System.Web.UI.WebControls.GridView{    protected override void OnSorting(GridViewSortEventArgs e)    {        if (!string.IsNullOrEmpty(this.SortExpression))        {            if (this.SortExpression.Equals(this.SortExpression))            {                bool isMultipleSortExpression;                SortDirection? sortDirection = GetSortDirection(this.SortExpression, out isMultipleSortExpression);                if (sortDirection.HasValue)                {                    // To undo bug in GridView.HandleSort(string sortExpression) and then in GridView.CreateDataSourceSelectArguments()                    e.SortDirection = SortDirection.Ascending;                }            }        }        base.OnSorting(e);    }    private SortDirection? GetSortDirection(string sortExpression, out bool isMultipleSortExpression)    {        SortDirection? sortDirection = null;        isMultipleSortExpression = false;        string[] strArray = sortExpression.Split(new char[] { ',' });        for (int i = 0; i < strArray.Length; i++)        {            string strA = strArray[i].Trim();            int length = strA.Length;            if ((length >= 5) && (string.Compare(strA, length - 4, " ASC", 0, 4, StringComparison.OrdinalIgnoreCase) == 0))            {                sortDirection = SortDirection.Ascending;            }            else if ((length >= 6) && (string.Compare(strA, length - 5, " DESC", 0, 5, StringComparison.OrdinalIgnoreCase) == 0))            {                sortDirection = SortDirection.Descending;            }            if (!sortDirection.HasValue)            {                break;            }        }        if (sortDirection.HasValue)        {            if (strArray.Length > 1)            {                isMultipleSortExpression = true;            }        }        return sortDirection;    }}

Enjoy it.

Filed in: ASP.NET | CodeProject