Better Custom Errors in ASP.NET: Custom Configuration Section

4/13/2008 8:12:06 PM

Technologies

  • ASP.NET
  • .NET Framework 2.0
  • IIS 6 / IIS 7

It's good practice to have a standalone and reusable HttpModule and currently the Better Custom Errors Module fails in that regard because it assumes the location of the friendly error page to be /BetterErrorMessage.aspx.

The Better Custom Errors Module also doesn't have the ability to map to specific custom error pages based upon http status codes. ASP.NET custom errors does have this feature.

To break out this dependency and add in this new feature we can create our own custom configuration section that our Better Custom Errors Module can use.

Creating a Custom Configuration Section

To start with lets first consider what is needed, a way to map status codes to an error page, a default error page, and it would be nice to have an easy way to turn on and off the better custom errors. A configuration section that looks something like this will work.

   1:  <mySection>
   2:      <betterCustomErrors enabled="true" defaultErrorUrl="/BetterErrorMessage.aspx">
   3:          <errorCodeMappings>
   4:              <add code="404" url="/BetterErrorMessage404.aspx" />
   5:          </errorCodeMappings>
   6:      </betterCustomErrors>
   7:  </mySection>

Next lets focus on creating a custom ConfigurationSection that implements just the enabled and defaultErrorUrl attributes. We create a class that inherits from ConfigurationSection defined in System.Configuration and implement the Enabled and DefaultErrorUrl properties.

   1:  public class BetterCustomErrorsConfigurationSection : ConfigurationSection
   2:  {
   3:      public BetterCustomErrorsConfigurationSection() { }
   4:   
   5:      [ConfigurationProperty("enabled", DefaultValue = "true", IsRequired = false)]
   6:      public bool Enabled
   7:      {
   8:          get { return (bool)this["enabled"]; }
   9:          set { this["enabled"] = value; }
  10:      }
  11:   
  12:      [ConfigurationProperty("defaultErrorUrl", IsRequired = true)]
  13:      public string DefaultErrorUrl
  14:      {
  15:          get { return (string)this["defaultErrorUrl"]; }
  16:          set { this["defaultErrorUrl"] = value; }
  17:      }
  18:  }
custom configuration error

As you can see setting up the attributes is rather easy. We just make use of the inherited indexer to store the values. The ConfigurationProperty attribute lets you define how the configuration section is handled. The enabled configuration attribute is set to true as default and isn't required. DefaultErrorUrl is required and if you don't specify it a configuration error gets thrown.

To make use of our Better Custom Errors Configuration Section it must be added as a config section and it must be the first node under configuration. What we end up with is a configuration as follows:

   1:  <configSections>
   2:      <sectionGroup name="mySection">
   3:          <section name="betterCustomErrors" 
   4:                   type="MyApplication.BetterCustomErrorsConfigurationSection" />
   5:      </sectionGroup>
   6:  </configSections>
   7:   
   8:  <mySection>
   9:      <betterCustomErrors enabled="false" defaultErrorUrl="/BetterErrorMessage.aspx">
  10:      </betterCustomErrors>
  11:  </mySection>

Accessing the values in our new custom configuration section then becomes as simple as grabbing the appropriate configuration section object and accessing the properties.

   1:  BetterCustomErrorsConfigurationSection settings = 
   2:      ConfigurationManager.GetSection("mySection/betterCustomErrors") as
   3:      BetterCustomErrorsConfigurationSection;
   4:   
   5:  string defaultErrorUrl = settings.DefaultErrorUrl;
   6:  bool enabled = settings.Enabled;

Creating the Error Code Mappings Configuration Collection

To create the error code mappings section two new configuration classes are needed. The Error Code Mapping Element that inherits from Configuration Element and the Error Code Mapping Collection that will inherit from Configuration Element Collection. The Error Code Mapping Element provides the implementation for one specific mapping while the collection will contain all the mappings.

   1:  public class ErrorCodeMappingElement : ConfigurationElement
   2:  {
   3:      public ErrorCodeMappingElement() { }
   4:   
   5:      [ConfigurationProperty("code", IsRequired = true)]
   6:      [IntegerValidator]
   7:      public int Code
   8:      {
   9:          get { return (int)this["code"]; }
  10:          set { this["code"] = value; }
  11:      }
  12:   
  13:      [ConfigurationProperty("url", IsRequired = true)]
  14:      public string Url
  15:      {
  16:          get { return (string)this["url"]; }
  17:          set { this["url"] = value; }
  18:      }
  19:  }

The Error Code Mapping Element is set up in a similar fashion as the Configuration Section. The xml attributes are defined with the Configuration Property attribute.

   1:  public class ErrorCodeMappingCollection : ConfigurationElementCollection
   2:  {
   3:      public ErrorCodeMappingCollection() { }
   4:   
   5:      public ErrorCodeMappingElement this[int statusCode]
   6:      {
   7:          get { return BaseGet((object)statusCode) as ErrorCodeMappingElement; }
   8:      }
   9:   
  10:      protected override ConfigurationElement CreateNewElement()
  11:      {
  12:          return new ErrorCodeMappingElement();
  13:      }
  14:   
  15:      protected override object GetElementKey(ConfigurationElement element)
  16:      {
  17:          return ((ErrorCodeMappingElement)element).Code;
  18:      }
  19:   
  20:      public bool Contains(int key)
  21:      {
  22:          foreach (object obj in this.BaseGetAllKeys())
  23:              if ((int)obj == key)
  24:                  return true;
  25:   
  26:          return false;
  27:      }
  28:  }

In the Error Code Mapping Collection a few things are going on. An int indexer is defined so that we can access the mappings in the collection by status (i.e. mappings[500]). Create New Element is overriden so that we can instantiate a new Error Code Mapping Element when requested. Get Element Key is overriden such that the status code is used as the key. Also a simple Contains method is defined that checks if a key is currently in the collection.

Now that our collection mapping classes are defined we must update the Configuration Section class.

   1:  public class BetterCustomErrorsConfigurationSection : ConfigurationSection
   2:  {
   3:      // collapsed for brevity
   4:   
   5:      [ConfigurationProperty("errorCodeMappings", IsRequired = false)]
   6:      public ErrorCodeMappingCollection ErrorCodeMappings
   7:      {
   8:          get { return (ErrorCodeMappingCollection)this["errorCodeMappings"]; }
   9:      }
  10:  }

After adding the mappings into the config we have the functionality we were looking for.

   1:  <mySection>
   2:      <betterCustomErrors enabled="true" defaultErrorUrl="/BetterErrorMessage.aspx">
   3:          <errorCodeMappings>
   4:              <add code="404" url="/BetterErrorMessage404.aspx" />
   5:          </errorCodeMappings>
   6:      </betterCustomErrors>
   7:  </mySection>

Accessing the error code mappings in code is straight forward.

   1:  BetterCustomErrorsConfigurationSection settings = 
   2:      ConfigurationManager.GetSection("mySection/betterCustomErrors") as
   3:      BetterCustomErrorsConfigurationSection;
   4:   
   5:  foreach(ErrorCodeMappingElement mapping in settings.ErrorCodeMappings)
   6:      Response.Write(string.Format("[{0},{1}]<br />", mapping.Code, mapping.Url));

Putting it All Together

Now that the custom configuration section is all in place we can make use of it in the Better Custom Errors Module. The changes to the better custom errors module are small. We just need to grab the configuration section, ignore processing if the module is disabled, and map to a specific error page based upon the http status code or return the default error page. What we end up with is the new an improved modular Better Custom Errors Module.

   1:  public class BetterCustomErrorsModule : IHttpModule
   2:  {
   3:      private static BetterCustomErrorsConfigurationSection _settings =
   4:          ConfigurationManager.GetSection("mySection/betterCustomErrors") as
   5:              BetterCustomErrorsConfigurationSection;
   6:   
   7:      public void Dispose() { }
   8:   
   9:      public void Init(HttpApplication context)
  10:      {
  11:          context.Error += new EventHandler(Context_Error);
  12:      }
  13:   
  14:      private void Context_Error(object sender, EventArgs e)
  15:      {
  16:          if (!_settings.Enabled) return;
  17:   
  18:          HttpApplication application = (HttpApplication)sender;
  19:          HttpContext context = application.Context;
  20:          HttpException httpException = context.Error as HttpException;
  21:   
  22:          if (httpException != null)
  23:              context.Response.StatusCode = httpException.GetHttpCode();
  24:          else
  25:              context.Response.StatusCode = 500;
  26:   
  27:          context.ClearError();
  28:          if (_settings.ErrorCodeMappings.Contains(context.Response.StatusCode))
  29:          {
  30:              ErrorCodeMappingElement mapping = 
  31:                  _settings.ErrorCodeMappings[context.Response.StatusCode];
  32:              context.Server.Transfer(mapping.Url);
  33:          }
  34:          else
  35:          {
  36:              context.Server.Transfer(_settings.DefaultErrorUrl);
  37:          }
  38:      }
  39:  }

Comments

anonymous

anonymous

5/16/2008 8:10:55 AM

hello

Rosco

Rosco

9/9/2009 12:08:48 AM

You don't need to create your own config section - just use the one .NET uses: customErrors. This code is what I use:

public class ErrorModule : IHttpModule

{

public void Dispose() { }

public void Init(HttpApplication context)

{

context.Error += new EventHandler(Context_Error);

}

public void Context_Error(object sender, EventArgs e)

{

HttpApplication application = (HttpApplication)sender;

HttpContext context = application.Context;

System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration("~");

SystemWebSectionGroup systemWeb = (SystemWebSectionGroup)configuration.GetSectionGroup("system.web");

CustomErrorsSection customErrorsSection = systemWeb.CustomErrors;

// If customerrors mode == off, then just let IIS handle it

if (customErrorsSection.Mode != CustomErrorsMode.Off)

{

// If mode == on or its a remote request, we want to get the pretty page

if (customErrorsSection.Mode == CustomErrorsMode.On || !context.Request.IsLocal)

{

Exception ex = context.Error;

HttpException httpException = ex as HttpException;

string sURL = customErrorsSection.DefaultRedirect;

if (httpException != null)

{

context.Response.StatusCode = httpException.GetHttpCode();

CustomErrorCollection customErrorsCollection = customErrorsSection.Errors;

CustomError customError = customErrorsCollection[context.Response.StatusCode.ToString()];

if (customError != null)

sURL = customError.Redirect;

// Log the actual cause

ex = httpException.GetBaseException();

}

else

{

context.Response.StatusCode = 500;

}

MMS_Logger.Write("", "Exception", ex);

context.ClearError();

context.Server.Transfer(sURL);

}

}

}

}

Internet Kasino Rating

Internet Kasino Rating

http://www.casino-bewertung.de/
12/28/2009 6:24:44 AM

Thank you so much. With your help and a simple, easy to follow step by step code for the xml integration in asp, I am finally able to surpass and understand how to include xml feeds.

Add Comment