Building a MVC2 Template, Part 7, Custom Web Errors and Adding Support for Elmah

2 Comments

In this installment we’ll add support for our custom web errors and add support for Elmah.

Custom Web Errors

First we need to write our specs. In the Controllers folder of the Nehemiah.Specs project add a class file named ErrorControllerSpecs.cs. Initially we’ll write a single generic error page. So there will be only a single test. Our specs look like this.

using System.Web.Mvc;
using Machine.Specifications;
using Nehemiah.Controllers;
<pre>

namespace Nehemiah.Specs.Controllers
{

    [Subject("Error Page")]
    public class when_an_error_occurs
    {
        static ErrorController controller;
        static ActionResult result;
        static string key;
        static string message;

        Establish context = () =>
        {
            controller = new ErrorController();
            key = "Message";
            message = "An error occurred while trying to process your request.";
        };

        Because of = () =>
        {
            result = controller.Index();
        };

        It should_display_a_generic_error_page = () =>
        {
            result.is_a_view_and().ViewName.ShouldBeEmpty();
        };

        It should_display_a_generic_error_message = () =>
        {
            result.is_a_view_and().ViewData[key].ShouldEqual(message);
        };

    }

}       // End Namespace

Obviously, this will fail to compile, since we do not have an ErrorController. So let’s create it in the Controllers folder of our main project.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Nehemiah.Controllers
{
    public class ErrorController : Controller
    {
        //
        // GET: /Error/
        public ActionResult Index()
        {
            ViewData["Message"] = "An error occurred while trying to process your request.";
            return View();
        }

    }
}

ProjectSolution07-01
We’ll need a simple view to start with. So add a new Error subfolder to the Views folder in the main project. Next create a new view named Index.aspx in our Error subfolder. Our view should look like the source code below.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 Error Processing Your Request
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2><%= Html.Encode(ViewData["Message"]) %></h2>
</asp:Content>

BuildReport07-01
Now rebuild our solution. Open the file Report.html located in the Nehemiah.Specs project folder. You should see that our spec has passed testing.

Next we need to enable our custom errors, so open the web.config file located in the main project. Location the tag customErrors. Right now we have a generic error for all errors, so this section should look like the following.

<customErrors mode="On" defaultRedirect="~/Error">
  <!--<error statusCode="403" redirect="~/Error/NoAccess" />-->
  <!--<error statusCode="404" redirect="~/Error/FileNotFound" />-->
</customErrors>

Open Global.asax.cs, as we need to add a new route.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace Nehemiah
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode,
    // visit <a href="http://go.microsoft.com/?LinkId=9394801">http://go.microsoft.com/?LinkId=9394801</a>
    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

            routes.MapRoute(
                "ErrorHandler", // Route name
                "{*path}",      // URL
                new { controller = "Error", action = "Index" }
            );

        }

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RegisterRoutes(RouteTable.Routes);
        }
    }
}

NehemiahErrorPage07-01
Now run the project. Then change the URL by appending some junk text to the end. You should see our generic error page.

You can add new actions to the ErrorController to handle 403, 404, and other errors if you choose. For now, I want just a single error action to handle all errors.

Adding Elmah Support

Elmah is an acronym for “Error Logging Modules and Handlers”. This should be a mandatory add-on for any and all Asp.Net applications. Elmah is easily added to any project without any code changes (Some web.config changes, but no code). It will log errors and email us if we desire. It also provides a web page for easy viewing.

Download Elmah from http://code.google.com/p/elmah/downloads/list. Extract the files to C:\_CodeVault\ELMAH.

Add references to Elmah.dll to the main project. This file is located in the folder C:\_CodeVault\ELMAH\bin\net-3.5\Release. There are also folders for .Net frameworks 1, 1.1, and 2.0.

ELMAH can log errors to several backends:

  • Microsoft SQL Server
  • Oracle
  • SQLite
  • Microsoft Access
  • VistaDB
  • XML
  • Memory

ProjectSolution07-02
I’m going to store the errors in a database, more specifically a Microsoft SQL Server Express database. Later when I develop an application using this template I can move it to SQL Server. Add a SQL Server Database to the App_Data folder in the main project. Since Nehemiah rebuilt Jerusalem, lets name the database file Jerusalem.mdf.

Next we need to edit the connectionStrings in web.config file in the main project. The connectionString we wish to use is named Nehemiah. One additional step needed is to replace occurrences of ApplicationServices with Nehemiah.

<connectionStrings>
  <add name="Nehemiah" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|Jerusalem.mdf;User Instance=true" providerName="System.Data.SqlClient"/>
</connectionStrings>

ProjectSolution07-03
Now everything points to our single database. We need to add a table and stored procedures to our database to support Elmah. There is a SQL script in the file we downloaded from the Elmah web site. Create a new folder named “SQL Scripts” in the main project. Next add a text file to our new folder and give it a name of Elmah.sql. Now paste the contents of C:\_CodeVault\ELMAH\db\SQLServer.sql into this file and save.

Now we need to run this script against our database. Open SQL Server Management Studio (Express version available here http://www.microsoft.com/downloads/details.aspx?FamilyId=C243A5AE-4BD1-4E3D-94B8-5A0F62BF7796&displaylang=en). Attach the Jerusalem.mdf database file. You will most likely need to close the solution in order to attach the database.

Once the database is attached paste the SQL script into a new Query window and run it. If it’s not in your paste buffer you can open it from our project or from the _CodeVault folder. When it’s run without errors, detach the database from SSMS and reopen our solution.

The next step is to edit the web.config file in the main solution.

In the configSections section add a new sectionGroup.

    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
    </sectionGroup>

Add the code below after the connectionStrings section. Notice I have commented out the section that will send emails. Also the “deny users” tag has been commented out. We will re-enable this once we add a membership provider.

  <elmah>
    <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="Nehemiah" />
    <security allowRemoteAccess="0" />
    <!--
    <errorMail from="<a href="mailto:elmah@example.com">elmah@example.com</a>"
             to="<a href="mailto:admin@example.com">admin@example.com</a>"
             cc="<a href="mailto:carboncopy@example.com">carboncopy@example.com</a>"
             subject="..."
             async="true|false"
             smtpPort="25"
             smtpServer="smtp.example.com"
             userName="johndoe"
             password="secret" />
    -->
  </elmah>
  <location path="elmah.axd">
    <system.web>
      <authorization>
        <!--<deny users="?" />-->
      </authorization>
      <httpRuntime requestLengthDiskThreshold="256" maxRequestLength="2097151" />
    </system.web>
  </location>

Add the code below to the httpHandlers section

      <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />

and this code to the httpModules section. Once again the information to send emails when an error occurs has been commented out. You can uncomment if you wish to receive emails when Elmah records an error.

      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <!--<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />-->

The last step is to modify the Site.Master file in our main project. Lets add an Elmah link to the menu. Add the following line to the menu list.

<li><a href="/elmah.axd">Elmah</a></li>

NehemiahErrorPage07-02
Run the project and append some junk text (“ShowMeAnError”) to the end of the URL. We see that our generic web error page is shown. Next click the Elmah link and our error is shown below.

ElmahErrors07-01

Wrap Up

Commit all changes to Subversion, except Jerusalem.mdf and Jerusalem.ldf. These two files will need to be added to Subversion using TortoiseSVN, after you close the project.

Then open http://localhost:8080 and verify that TeamCity build the project without any errors.

References

Shout it


Kick It on DotNetKicks.com
Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)

2 Comments (+add yours?)

  1. click here
    Dec 12, 2011 @ 06:53:59

    Great post. A comprehensive roundup of all the good sources I already knew of and few I didn’t. I am always looking for new ways to make my site more useful to my users. Thanks a bunch.

  2. Carlos
    Feb 09, 2012 @ 05:36:41

    Thanks for this great post. I love your srceet ‘change before you have to’. Doing a trial run helps too.

Leave a Reply

Comment moderation is enabled. Your comment may take some time to appear.