Click here to Skip to main content
Click here to Skip to main content

MVC Basic Site: Step 2 - Exceptions Management

, 25 Oct 2013
Rate this:
Please Sign up or sign in to vote.
This second article from the "MVC Basic Site" series presents in details the exceptions management rules and their implementation for an ASP.NET MVC web site, and provides some utile base classes and source code for Logging and Exceptions Management that can be reused.

MVC Basic Site

Table of Contents

Introduction

MVC Basic Site is intended to be a series of tutorial articles about the creation of a basic and extendable web site that uses ASP.NET MVC.

The first article from this series, named MVC Basic Site: Step1-Multilingual Site Skeleton, was focused mainly on the creation of a multilingual web site skeleton by using ASP.NET MVC 3.0. Also user authentication and registration created from scratch was described there.

This second article presents, in details, the exceptions management rules and their implementation for an ASP.NET MVC web site, and provides some utile base classes and source code for Logging and Exceptions Management that can be reused (with very small changes) not only in other ASP.NET sites but also generally in any .NET project.

MVC Basic Site is developing by using an incremental and iterative methodology, this means that each new step add more functionalities to the solution from the previous step, so the source code provided for download in this article contains all functionalities implemented until now (from both articles).

Note that all provided source code is very well commented and clean and there should be no problem in reading and understanding it.

Exceptions management is very important in the development of any software solution. If exceptions management is ignored or not properly implemented, this will have a bad impact in the quality and performance of the software solution.

Software Environment

  • .NET 4.0 Framework
  • Visual Studio 2010 (or Express edition)
  • ASP.NET MVC 4.0
  • SQL Server 2008 R2 (or Express Edition version 10.50.2500.0)

Exceptions Management rules in the MVC Basic Site

In each software project, at the beginning of project development, the software development rules that must be followed by the team should be defined. Part of these rules should be the exceptions management rules.

In this chapter I describe the exceptions management rules used by me in the MVC Basic Site solution, but these rules could be seen as practical recommendations that could be used in any ASP.NET solution and with some adaptations in any .NET solution.

In general exception management uses the try, catch, and finally or using keywords to manage actions that may not succeed (like accessing a database table, accessing a file from the file system, using memory allocation, sending emails, etc.), to handle failures when you decide that it is reasonable to do so, and to clean up the used resources. Note that exceptions can be generated by the .NET Framework, by used third-party libraries, or by application code by using the throw keyword.

The exception management basic rules used in the MVC Basic Site solution are:

  1. Use a finally block to release any kind of resources that are not disposable (does not implement IDisposable) but require some release actions, for example to close any streams or files that were opened in the try block.
  2. Handle access to the disposable resources that could generate exceptions and do not forgot to dispose the used resource at the end by using try-catch-finally or using statements;
  3. Do not use try-catch where not needed, use another approach like if-else sentences. For example checking for null before performing any operation over an object that may be null can significantly increase performance by avoiding exceptions.
  4. Do not catch and throw again the same exception type.
  5. For the entire solution use a new exception class derived from ApplicationException, in our case named BasicSiteException, to identify exceptions generated by the database or logic layer.;
  6. For the entire solution use a utile class for writing messages and exceptions information into the Windows event log. In our solution this class is named MvcBasicLog (see details in the next chapters).
  7. Catch an exception at the database or logic level only if it is necessary to add more information in the re-thrown exception, and use objects of type MvcBasicException for this. Do not forgot to include the original exception as an inner exception in the re- thrown exception.
  8. The expected exceptions should be caught finally at the user interface level and then the exception information (message and stack trace) must be written into the event log by using the MvcBasicLog class and a friendly error message should be shown to the user in the current page.
  9. Manage unauthorized access exceptions to prevent the user from accessing the site main functionalities without being authenticated first and without the proper rights.
  10. Do not forgot to manage unhandled exceptions (unexpected exceptions) at the user interface level, and also in this case the error information must be written down into the event log and a friendly error message shown in the Error page.
  11. Avoid unnecessary unhandled exceptions to be generated by using the proper methods. For example in the case of using Entity Framework, use FirstOrDefault() then test with null in place of First() like in the next code:
if (ModelState.IsValid)
{
    //
    // Verify the user name and password.
    //
    User user = _db.Users.FirstOrDefault(item => item.Username.ToLower() == 
      model.Username.ToLower() && item.Password == model.Password);
    if (user == null)
    {
        ModelState.AddModelError("", Resources.Resource.LogOnErrorMessage);
        //
        return View(model);
    }
    else
    {
        //
        // User logined succesfully ==> create a new site session!
        //
        FormsAuthentication.SetAuthCookie(model.Username, false);
        //
        SiteSession siteSession = new SiteSession(_db, user);
        Session["SiteSession"] = siteSession; // Cache the user login data!
        //
        // Log a message about the user LogOn.
        //
        MvcBasicLog.LogMessage(string.Format("LogOn for user: {0}", siteSession.Username));
        //
        // Redirect to Home page.
        //
        return RedirectToAction("Index", "Home");
    }
}

Writing Messages and Exceptions into Log

In any software solution it is very important to preserve messages, errors, and exception data generated during software usage into a log. This way the messages and exceptions data can be accessed and analyzed at a later time. In Windows the better place that should be used for saving log messages is the Windows Event Log.

MvcBasicLog is a class is the utile class used for writing messages and exceptions information into the Windows Application Event Log.

Like you can see from the class diagram above, this class has a set of public static methods used to log messages, errors, and exceptions into the Windows event log. All these messages will be written into the same log source (defined as a static value in the _logSource static member), and for some methods the user can specify the name of the category to be used as a prefix of the message.

All these public methods use the private method AddLogLine to do the work of writing into the event log source.

private static void AddLogLine(string logMessage, bool isError)
{
    EventLog log = new EventLog();
    log.Source = _logSource;
    //
    try
    {
        log.WriteEntry(logMessage, (isError ? EventLogEntryType.Error : EventLogEntryType.Information));
    }
    catch (System.Security.SecurityException ex)
    {
        //
        // In Web app you do not have right to create event log source and
        // the log source must be created first by using the provided CreateEventLogEntry project!
        //
        throw new ApplicationException("You must create the event log entry " + 
          "for our source by using CreateEventLogEntry project!", ex);
    }
    catch
    {
        //
        // The log file is to large, so clear it first.
        //
        log.Clear();
        log.WriteEntry(logMessage, (isError ? EventLogEntryType.Error : EventLogEntryType.Information));
    }
    //
    log.Close();
}

To write a message notification into the log you have to do this:

MvcBasicLog.LogMessage(string.Format("LogOn for user: {0}", siteSession.Username));

You can check the Windows application event log by using the “Event Viewer” tool.

Event Viewer - Message Info

Note that in the case of LogException methods the entire exception data will be written into the event log, and in this case the results from the event log will be something like the next one.

Event Viewer - Exception Info

In the picture above, you can see that the event log details the exception messages and exception stack trace including the source code line that generates the exception.

So for each “Error” entry from the event log that is associated with the exception generated during the usage of the program, the developers can identify easily the source code line and the context that generates the problem.

Manage Expected Exceptions

When in the source code are implemented actions that may not succeed, like accessing a database table, accessing file from the file system, using memory allocation, sending emails, and so one, these actions could generate expected exceptions. Also some parts of the source code solution could throw expected exceptions in the implementation of their logic if some logic rules of the application logic are broken (like user rights access violation).

Management of the expected exception is the main part of the exception management process and for doing this it is appropriate to create and use a solution specific exception base class.

Other important aspect in this management process is that for many expected exceptions, that are generated from the source code lower layers, a notification message about the problem should be shown also to the user in the user interface layer; so the information about the original exception have to be propagated from layer to layer before to be finally handled at the user interface layer. During this propagation the exception data should accumulate more context information that will describe and localize the problem, and all this information should be saved into a log and for future use (like log reports and problems fixing).

The base class used by me for managing the expected exception is the MvcBasicException class. It is derived from ApplicationException, and is marked as serializable by using the [Serializable] attribute.

Note: The serialization happens in a lot of places without them knowing it; the serialization mechanisms are the basis for all cross application domain calls, even when they are inside the same process boundary, for example from a DLL of the logic layer to the UI application layer. So because we what that the entire exception data (exception message and stack trace) to be preserved and transferred between application layers, we must mark our exception class with Serializable attribute and also we have to provide a protected constructor used for serialization.

Like you can see from the class diagram above this class has three constructors that can be used in the next situations:

  • The protected constructor initializes a new instance of the MvcBasicException class with serialized data. This constructor is used internally by the .NET framework for passing all exception data between application layers.
  • Initializes a new instance of the MvcBasicException class with a specified error message that should explain the reason for the exception from our application point of view.
  • Initializes a new instance of the MvcBasicException with a specified error message and a reference to the inner exception that is the cause of this exception. This constructor has at first parameter an error message that should explains the reason for the exception from our application point of view, and as second parameter the inner exception that was the cause of the current exception.

In the logic layer's entities classes, the management of the expected exceptions should be done like in the next example.

public static int GetNormalSearchCount(int userID, params object[] parameters)
{
    MvcBasicSiteEntities dataContext = null;
    //
    try
    {
        dataContext = new MvcBasicSiteEntities();
        return dataContext.ExecuteStoreCommand("GetNormalSearchCount", parameters);
    }
    catch (System.Data.SqlClient.SqlException exception)
    {
        // 
        // Manage the SQL expected exception by generating a MvcBasicException 
        // with more info added to the orginal exception.
        //
        throw new MvcBasicException(string.Format(
          "GetNormalSearchCount for user: {0}", userID), exception);
    }
    finally
    {
        //
        // Dispose the used resource.
        //
        if(dataContext != null)
            dataContext.Dispose();
    }
}

You can see in the code above that we are trying to invoke a SQL command with parameters; so because the access to the database could generate an exception, and also because the used connection to the database must be released in all possible cases, I use a try-catch-finally block that contains the next actions:

  • In the try section I create the data entity context object used to access the database, then I use the data context object to invoke the SQL command.
  • In the catch section, I catch only the expected exception that I want to manage. In our case only the SqlExeption exception. Then I handle the exception by creating an object of type MvcBasicException that will contains additional information used to identify the current given user and that stores also the original SQLException information into its inner parameter. The resultant exception object with all the needed data are then thrown to the upper layer of the application.
  • In the finally section I dispose the used resource, in my case the data entity context used to access the database. Note that before we dispose the data context object, I test if it was successfully created, because its constructor may generate a SQLException and in that case the data context object will be null!

In the user interface layer the expected exceptions management in this case is handled in the AcoountController next method.

public ActionResult TestExpectedException()
{
    SiteSession siteSession = this.CurrentSiteSession;
    //
    try
    {
        //
        // Invoke a method that could generate an exception!
        //
        int count = MvcBasic.Logic.User.GetNormalSearchCount(
                       siteSession.UserID, new object[] { "al*", "231" });
        //
        // TO DO!
        //...

    }
    catch (MvcBasicException ex)
    {
        MvcBasicLog.LogException(ex);
        ModelState.AddModelError("", Resources.Resource.ErrorLoadingData);
    }
    //
    // Stay in MyAcount page.
    //
    return View("MyAccount");
}

In the code above, you can see how expected exceptions are handled at the user interface layer. First the invocation of the logic method that could generate the expected exception is executed into a try block and then, in the catch, an exception of type MvcBasicException is expected and handled. The actions for handling an expected exception at the user interface layers are the next ones:

  • Save the exception data (message and stack trace) into the log (see previous chapter for details);
  • Show a notification error message to the user by using a multilingual message from our resources files.

To show the error message to the user in the _Header partial view (used in the site layout), I added an object of type ValidationSummary (but other approaches could use Label controls, error message pages, or popup message windows).

<div class="headerTitle">
    @Resources.Resource.HeaderTitle
</div>
@if (!(Model is LogOnModel))
{
    <div class="errorMessage">
        @Html.ValidationSummary(true)
    </div>
}

Note that into a view only one validation summary should exist (otherwise the error messages will be shown multiple times), so in the code above there is an if used to avoid the creation of the validation message in the case of the LogOn page that already has one.

We will test all of these Log On into the MVC Basic site by using the credentials of an existing user, for example username: Ana and password: ana. After the login, you should access the “My Account” menu and the next page will be shown.

MVC Basic Site - My Account Page

Now if you click on the “Test Expected Exception” link from the page above, the expected exception will be managed successfully by our site, and an error message will be shown in the page header like in the picture below.

MVC Basic Site - Test Expected Exception

Also if you open the Event Viewer in the Windows logs you will see a new entry from our site in the Application section.

Event Viewer - Expected exception info

Like you can see in the picture above, in the event log, the exception message and stack trace is saved, including our addition data added (current user ID), the original exception message, and the source code lines. All these information could be used to analyze, report, and/or fix the problem.

Manage Unauthorized Access Exceptions

Unauthorized Access Exceptions are special exceptions that can occur in ASP.NET MVC site exceptions, in the case when some user tries to access a page or action that has no authorization (rights) to access and/or that requires authentication.

All these exceptions are managed solely for all controllers in the BaseController class, by overriding the OnException method (that is inherited from the the MVC framework Controller class).

protected override void OnException(ExceptionContext filterContext)
{
    if (filterContext.Exception is UnauthorizedAccessException)
    {
        //
        // Manage the Unauthorized Access exceptions
        // by redirecting the user to Home page.
        //
        filterContext.ExceptionHandled = true;
        filterContext.Result = RedirectToAction("Home", "Index");
    }
    //
    base.OnException(filterContext);
}

Like you can see in the code above, we are handling these exceptions simply by redirecting the user to the home page.

Note that in the OnException method other special exceptions could be filtered and handled like I did for unauthorized access exceptions.

In ASP.NET MVC the management of unauthorized access to site functionalities should be done using the [Authorize] attribute. So in all controllers' public actions that require authentication we must use this attribute like in the text below.

[Authorize]
public ActionResult MyAccount()
{
    // TO DO!
    return View();
}

To test this start the MVC Basic site and, without Log On, try to access the next action from our site by using the URL: http://localhost:50646/Account/MyAccount.

Note that because this action requires authorization, this access will redirect you to the Log On page.

MVC Basic Site - Log On Page

Manage Unhandled Exceptions

Unhandled Exceptions are all exceptions that can occur in an application and are not handled in the source code as expected exceptions. Here we also included application bugs (errors and problems).

To enable unhandled exceptions management, in the site web.config the following line must be add/modified:

<customErrors mode="On"/>

When an unhandled exception is thrown, the ASP.NET MVC framework will activate the Error.cshtml page. So the code that manages all unhandled exceptions must be in the Error view (see details bellow).

@using MvcBasic.Logic
@using MvcBasicSite.Models
@model System.Web.Mvc.HandleErrorInfo
@{
    ViewBag.Title = "Error";
    //
    // Log out the user and clear its cache.
    //
    SiteSession.LogOff(this.Session);
    //
    // Log the exception.
    //
    MvcBasicLog.LogException(Model.Exception);
}
<meta http-equiv="refresh" content="5;url=/Home/Index/" />
<h2>@Resources.Resource.ErrorPageMessage</h2>

Like you can see from the source code above, the management of unhandled exceptions contains four actions:

  1. Log Off the current user by invoking the following method:
    public static void LogOff(HttpSessionStateBase httpSession)
    {
        //
        // Write in the event log the message about the user's Log Off.
        // Note that could be situations that this code was invoked from Error page 
        // after the current user session has expired, or before the user to login!
        //
        SiteSession siteSession = (httpSession["SiteSession"] == null ? 
           null : (SiteSession)httpSession["SiteSession"]);
        if(siteSession != null)
            MvcBasicLog.LogMessage(string.Format("LogOn for user: {0}", siteSession.Username));
        //
        // Log Off the curent user and clear its site session cache.
        //
        FormsAuthentication.SignOut();
        httpSession["SiteSession"] = null;
    }
  2. Write the exception data into the event log by using the MvcBasicLog class described above.
  3. Show a general error message (from resources files) to the user.
  4. Redirect after 5 seconds the user to the Home page.

To test all of these, Log On into the MVC Basic site by using the credentials of an existing user, then access the “My Account” menu and the next page will be shown.

MVC Basic Site - Test Unhandled Exceptions

Now if you click on the “Test Unhandled Exception” link from the page above, an unhandled exception generation will be simulated in the next AccountController action.

public ActionResult TestUnhandledException()
{
    //
    // Next line of code will try to open an view that does not exist ==> Exception.
    //
    return View();
}

Then the unhandled exception will be managed by our source code from the Error view (described above), the user will be Logged Off (note that the menu will also be changed), and the user will see the error message into the Error page.

MVC Basic Site - Error Page

After 5 seconds the user will be redirected automatically to the site Home page.

Note that the exception data will be saved into the event log. If you open the Event Viewer in the Windows Logs you will see a new Error entry from our site in the Application section.

Event Viewer - Unhandled Exception Info

By analyzing the error from the event log, you can see the details about the exception and the source code line that generates it. (In our case there is access to a view page that does not exist.)

Upgrading from ASP.NET MVC3 to ASP.NET MVC4

For upgrading the solution from ASP.Net MVC3.0 to ASP.NET MVC4.0 I used the manually steps indicated in MVC4 release notes.

After upgrading I encounter the next two problems:

  1. There were some errors in the latest version of the next java script: jquery.unobtrusive-ajax.min.js.

    I solved these errors by creating a new project of type ASP.NET MVC4.0 and by replacing the latest versions of the jQuery scripts, which I had in my solution after the updating steps, with the scripts from the new project.

  2. The protected method ExecuteCore() from BaseControler class was not invoked anymore on each postback by the ASP.NET MVC4.0 framework, so the change of the current used language did not work anymore.

    To solve this problem, in my BaseController class, I overridden the property DisableAsyncSupport like in the next source code:

    protected override bool DisableAsyncSupport
    {
        get { return true; }
    }
    

So now there is a new ZIP file attached to this article that contains the upgrading version of the MVC Basic Site solution that works with ASP.NET MVC4.0.

Before Running this Code

Before running this code, you should do the next steps:

  1. Create a new entry into the Event Log by running the CreateEventLogEntry application as Administrator (the CreateEventLogEntry application source code is provided as part of our solution).
  2. Create a database named MvcBasicSite in your SQL Server (or SQL Express), then restore the provided database MvcBasicSiteDatabase.bak on it.
  3. Modify the connection string into the Web.config file of the MvcBasicSite web application according to your settings from step 2.

References

History

  • 9 February, 2013: Version 1.0.0.1: Draft version.
  • 14 February, 2013: Version 1.0.0.2: Small changes after the review of the first published version.
  • 21 February, 2013: Version 1.0.0.3 - Basic Site steps.
  • 24 February, 2013: Version 1.0.0.4 - Some details added.
  • 2 March, 2013: Version 1.0.1.1 - Upgrading to ASP.NET MVC 4.0.
  • 23 April, 2013: Version 1.0.1.2 - Update the Basic Site steps.
  • 18 May, 2013: Version 1.0.1.3 - Update the Basic Site steps.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Raul Iloc
Architect SoftCon IT Services
Romania Romania
I'm currently Project Manager and Software Architect for .NET Development at SOFTCON IT Services SRL Timisoara a subsidiary of SOFTCON AG Munich Germany.
 
I am a senior software engineer with about 18 years working experience in software development and about 13 years as Project Manager and Architect.
I have about 12 years working experience in C# and .NET, 7 years working experience in C++ and Visual C++, 2+ years working experience in Java and J2EE.

Comments and Discussions

 
GeneralRe: My vote of 5 PinmemberRaul Iloc8-Mar-13 4:15 
GeneralRe: My vote of 5 PinmemberRaul Iloc11-Apr-13 22:04 
GeneralMy vote of 5 PinmemberKundan Singh Chouhan5-Mar-13 5:39 
GeneralRe: My vote of 5 PinmemberRaul Iloc5-Mar-13 19:02 
Thank you for your vote!
Raul Iloc

GeneralRe: My vote of 5 PinmemberRaul Iloc11-Apr-13 22:05 
QuestionExcellent article with Design discussion Pinmembergillindsay26-Feb-13 5:59 
AnswerRe: Excellent article with Design discussion PinmemberRaul Iloc26-Feb-13 18:57 
AnswerRe: Excellent article with Design discussion PinmemberRaul Iloc11-Apr-13 22:05 
GeneralMy vote of 5 PinmemberPascen24-Feb-13 11:12 
GeneralRe: My vote of 5 PinmemberRaul Iloc24-Feb-13 19:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 25 Oct 2013
Article Copyright 2013 by Raul Iloc
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid