Handling database exceptions in Dynamic Data

This post was prompted from a forum thread in which the user wanted to display database errors in a Dynamic Data page, instead of the default behavior that ends up with an unhandled exception (or an AJAX error with partial rendering).

When using Linq To Sql, this can be done fairly easily in a couple different ways, by wrapping the exception in a ValidationException.  To do it globally for a DataContext, you can do something like this:

 public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode) {
    try {
        base.SubmitChanges(failureMode);
    }
    catch (Exception e) {
        throw new ValidationException(null, e);
    }
}

And to do it with more granularity, you can use one of the partial methods on the DataContext.  e.g.

 partial void DeleteCategory(Category instance) {
    try {
        ExecuteDynamicDelete(instance);
    }
    catch (Exception e) {
        throw new ValidationException(null, e);
    }
}

A few notes about this:

  1. By passing null as the text, it uses the original exception’s text.  You very well may want to use a custom error message instead
  2. Instead of doing it ‘blindly’, you may want to look at the database exception and selectively decide to wrap it or not.  If you don’t want to wrap it, just use the ‘throw;’ statement to rethrow it unchanged.
  3. There is a small issue in the default templates that you need to fix if you want to handle DB exceptions happening during a Delete: in both list.aspx and details.aspx, you’ll find CausesValidation="false" .  You need to either get rid of it or set it to true (which is the default).

But now, let’s try to do the same thing with Entity Framework.  Unfortunately, ObjectContext doesn’t have as many useful hooks as Linq To Sql’s DataContext (this will change in the next version), so the techniques above are not available.

However, there is a fairly easy workaround that can be used, which involves using a custom derived DynamicValidator control.  Here are the steps to do this (full sample attached at the end of this post).

First, let’s create the derived DynamicValidator, as follows.  I’ll let the comments speak for themselves:

 /// <summary>
/// By default, Dynamic Data doesn't blindly display all exceptions in the page,
/// as some database exceptions may contain sensitive info.  Instead, it only displays
/// ValidationExceptions.
/// However, in some cases you need to display other exceptions as well.  This code
/// shows how to achieve this.
/// </summary>
public class MyDynamicValidator : DynamicValidator {

    protected override void ValidateException(Exception exception) {
        // If it's not already an exception that DynamicValidator looks at
        if (!(exception is IDynamicValidatorException) && !(exception is ValidationException)) {
            // Find the most inner exception
            while (exception.InnerException != null) {
                exception = exception.InnerException;
            }

            // Wrap it in a ValidationException so the base code doesn't ignore it
            if (ExceptionShouldBeDisplayedInPage(exception)) {
                exception = new ValidationException(null, exception);
            }
        }

        // Call the base on the (possibly) modified exception
        base.ValidateException(exception);
    }

    private bool ExceptionShouldBeDisplayedInPage(Exception e) {
        // This is where you may want to add logic that looks at the exception and
        // decides whether it should indeed be shown in the page
        return true;
    }
}

Then you need to make all the pages use it.  The simplest way to do it is via a little know but powerful feature: tag remapping.  Here is what you need to have in web.config:

     <pages>
      <tagMapping>
        <add tagType="System.Web.DynamicData.DynamicValidator" mappedTagType="MyDynamicValidator"/>
      </tagMapping>
    </pages>

And then you also need to do the same as the 3rd bullet point above: get rid of CausesValidation="false" in the pages.

And that’s all!  You should now see the error messages directly in the page.

DynamicDataEntityDbErrorHandling.zip