HttpModule for Performing a Custom Action for Certain URLs
January 13th, 2005

I wrote this collection of classes over the holidays as part of a side project and forgot about it. Today at work I needed basically exactly the same thing, but unfortunately I didn’t have it handy and had to recreate it from scratch.

This is a way to execute any action you please for requests against url’s matching one or more regular expressions. Today at work, I used it to install a response filter for a specific set of .aspx pages. I can think of at least a few more reasons someone might want something like this, so I’m posting it.


    using System;
    using System.Text.RegularExpressions;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    
    namespace McKinley.Web
    {
        /// <summary>
        /// An abstract <see cref="T:System.Web.IHttpModule"/> that checks
        /// the url of each incoming request against one or more regular
        /// expressions. When a match is found, a custom action is
        /// performed.
        /// </summary>
        public abstract class BaseRegexModule : IHttpModule
        {
            private RegexChain _chain;
    
            /// <summary>
            /// Reads the list of regular expressions to match from
            /// the configuration settings. Hooks the beginning of
            /// each request to perform the checks.
            /// </summary>
            public void Init(HttpApplication context)
            {
                Hashtable settings = (Hashtable)ConfigurationSettings.GetConfig(this.ConfigSectionName);
                if(settings != null)
                {
                    _chain = new RegexChain();
                    foreach(object val in settings.Keys)
                    {
                        string pattern = val as string;
                        if(pattern != null && pattern.Length > 0)
                        {
                            _chain.Add(pattern);
                        }
                        else
                        {
                            throw new ConfigurationException(
                                "The regular expression pattern may not be empty.");
                        }
                    }
                }
                context.BeginRequest += new EventHandler(BeginRequest);
            }
    
            /// <summary>
            /// Tries to match the raw url against the list of expressions
            /// given to the module. If a match is found, calls the abstract
            /// <see cref="M:CustomAction"/> method.
            /// </summary>
            private void BeginRequest(object sender, EventArgs e)
            {
                HttpApplication application = sender as HttpApplication;
                if(application != null)
                {
                    HttpRequest request = application.Request;
                    if(request != null && _chain.IsMatch(request.RawUrl))
                    {
                        CustomAction(application);
                    }
                }
            }
    
            /// <summary>
            /// This must be overridden to provide the name of the configuration
            /// section for the concrete module class. The section is presumed
            /// to use a <see cref="T:System.Configuration.DictionarySectionHandler"/>.
            /// </summary>
            protected abstract string ConfigSectionName { get; }
    
            /// <summary>
            /// This must be overridden to define a custom action to be performed
            /// when one of the regular expressions are matched against the
            /// request's URL.
            /// </summary>
            protected abstract void CustomAction(HttpApplication context);
    
            public void Dispose() { }
    
            public BaseRegexModule() { }
        }
    
        /// <summary>
        /// This is an efficient utility class for a list of
        /// regular expressions evaluated together.
        /// </summary>
        internal class RegexChain
        {
            private class Node
            {
                public Regex Expression;
                public Node Next;
    
                public Node(Regex r)
                {
                    Expression = r;
                }
             }
    
            private Node _head;
            private Node _tail;
    
            /// <summary>
            /// Returns true if the given string is a match
            /// for any of the regular expressions in the chain.
            /// </summary>
            public bool IsMatch(string input)
            {
                Node n = _head;
                while(n != null)
                {
                    if(n.Expression.IsMatch(input))
                    {
                        return true;
                    }
                    n = n.Next;
                }
                return false;
            }
    
            /// <summary>
            /// Adds the given pattern to the chain of regular
            /// expressions.
            /// </summary>
            public void Add(string pattern)
            {
                Node n = new Node(new Regex(pattern));
                if(_head == null)
                {
                    _head = n;
                }
                else
                {
                    _tail.Next = n;
                }
                _tail = n;
            }
        }
    
        /// <summary>
        /// This is a <see cref="T:System.Configuration.IConfigurationSectionHandler"/>
        /// that can be used with a class derived from
        /// <see cref="T:McKinley.Web.BaseRegexModule"/>.
        /// </summary>
        public class RegexModuleSectionHandler : DictionarySectionHandler
        {
            protected override string KeyAttributeName
            {
                get { return "pattern"; }
            }
        }
    }

  

And there you have it. Here’s an extremely simple example class that uses this to set a cookie for matching url’s.


    public class ExampleRegexModule : BaseRegexModule
    {
        public ExampleRegexModule() { }
    
        protected override void CustomAction(System.Web.HttpApplication context)
        {
            context.Response.AppendCookie(new HttpCookie("Oscar", "Is god"));
        }
    
        protected override string ConfigSectionName
        {
            get { return "mckinley.web/exampleRegexModule"; }
        }
    }

  

And finally, here are the web.config entries you’d need to install this HttpModule.


    <configSections>
        <sectionGroup name="mckinley.web">
            <section
                name="exampleRegexModule"
                type="McKinley.Web.RegexModuleSectionHandler, McKinley.Web"
            />
        </sectionGroup>
    </configSections>
    
    <mckinley.web>
        <exampleRegexModule>
            <add pattern="somepage.aspx" />
        </exampleRegexModule>
    </mckinley.web>
    
    <system.web>
        <httpModules>
            <add name="example"
                    type="McKinley.Web.ExampleRegexModule, McKinley.Web" />
        </httpModules>
    <system.web>