Error Handling

Templates uses Exceptions for error flow control flow that by default will fail fast by emitting the Exception details at the point where the error occured before terminating the Stream and rethrowing the Exception which lets you for instance catch the Exception in Unit Tests.

Handled Exceptions

You can instead choose to handle exceptions and prevent them from short-circuiting page rendering by assigning them to an argument using either the assignError filter which will capture any subsequent Exceptions thrown on the page:

Alternatively it can be specified on each call-site where it will capture the Exception thrown by the filter:

An easy way to prettify errors in Web Pages is to use HTML Error Filters.

Unhandled Exceptions Behavior

If Exceptions are unassigned they're considered to be unhandled. The default behavior for TemplateContext is to rethrow Exceptions, but as ServiceStack's TemplatePagesFeature is executed within the context of a HTTP Server it's default is configured to:

new TemplateContext {
    SkipExecutingFiltersIfError = true
}

Which instead captures any unhandled Exceptions in PageResult.LastFilterError and continues rendering the page except it skips evaluating any subsequent filters and instead only evaluates the filters which handle errors, currently:

ifError Only execute the filter if there's an error:
{{ ifError | select: FAIL! {{ it.Message }} }}
lastError Returns the last error on the page or null if there was no error, that's passed to the next filter:
{{ lastError | ifExists | select: FAIL! {{ it.Message }} }}
htmlError Display detailed htmlErrorDebug when in DebugMode otherwise display a more appropriate end-user htmlErrorMessage.
htmlErrorMessage Display the Exception Message
htmlErrorDebug Display a detailed Exception

This behavior provides the optimal UX for developers and end-users as HTML Pages with Exceptions are still rendered well-formed whilst still being easily able to display a curated error messages for end-users without developers needing to guard against executing filters when Exceptions occur.

Controlling Unhandled Exception Behavior

We can also enable this behavior on a per-page basis using the skipExecutingFiltersOnError filter:

Here we can see that any normal filters after exceptions are never evaluated unless they're specifically for handling Errors.

Continue Executing Filters on Unhandled Exceptions

We can also specify that we want to continue executing filters in which case you'll need to manually guard filters you want to control the execution of using the ifNoError or ifError filters:

Accessing Page Exceptions

The last Exception thrown are accessible using the lastError* filters:

Throwing Exceptions

We've included a few of the popular Exception Types, filters prefixed with throw always throws the Exceptions below:

Filter Exception
throw Exception(message)
throwArgumentException ArgumentException(message)
throwArgumentNullException ArgumentNullException(paramName)
throwNotSupportedException NotSupportedException(message)
throwNotImplementedException NotImplementedException(message)
throwUnauthorizedAccessException UnauthorizedAccessException(message)
throwFileNotFoundException FileNotFoundException(message)
throwOptimisticConcurrencyException OptimisticConcurrencyException(message)
You can extend this list with your own custom filters, see the Error Handling Filters for examples.

These filters will only throw if a condition is met:

Filter Exception
throwIf message | Exception | if(test)
throwArgumentNullExceptionIf paramName | ArgumentNullException | if(test)
ifthrow if(test) | Exception(message)
ifThrowArgumentNullException if(test) | ArgumentNullException(paramName)
ifThrowArgumentException if(test) | ArgumentException(message)
if(test) | ArgumentException(message, paramName)

Ensure Argument Helpers

The ensureAll* filters assert the state of multiple arguments where it will either throw an Exception unless all the arguments meet the condition or return the Object Dictionary if all conditions are met:

The ensureAny* filters only requires one of the arguments meet the condition to return the Object Dictionary:

Fatal Exceptions

The Exception Types below are reserved for Exceptions that should never happen, such as incorrect usage of an API where it would've resulted in a compile error in C#. When these Exceptions are thrown in a filter or a page they'll immediately short-circuit execution of the page and write the Exception details to the output. The Fatal Exceptions include:

Rendering Exceptions

The OnExpressionException delegate in Page Formats are able to control how Exceptions in filter expressions are rendered where if preferred exceptions can be rendered in-line in the filter expression where they occured with:

var context = new TemplateContext {
    RenderExpressionExceptions = true
}.Init();

The OnExpressionException can also suppress Exceptions from being displayed by capturing any naked Exception Types registered in TemplateConfig.CaptureAndEvaluateExceptionsToNull and evaluate the filter expression to null which by default will suppress the following naked Exceptions thrown in filters:

Implementing Filter Exceptions

In order for your own Filter Exceptions to participate in the above Template Error Handling they'll need to be wrapped in an StopFilterExecutionException including both the Template's scope and an optional options object which is used to check if the assignError binding was provided so it can automatically populate it with the Exception.

The easiest way to Implement Exception handling in filters is to call a managed function which catches all Exceptions and throws them in a StopFilterExecutionException as seen in OrmLite's TemplateDbFilters:

T exec<T>(Func<IDbConnection, T> fn, TemplateScopeContext scope, object options)
{
    try
    {
        using (var db = DbFactory.Open())
        {
            return fn(db);
        }
    }
    catch (Exception ex)
    {
        throw new StopFilterExecutionException(scope, options, ex);
    }
}

public object dbSelect(TemplateScopeContext scope, string sql, Dictionary<string, object> args) => 
    exec(db => db.SqlList<Dictionary<string, object>>(sql, args), scope, null);

public object dbSelect(TemplateScopeContext scope, string sql, Dictionary<string, object> args, object op) => 
    exec(db => db.SqlList<Dictionary<string, object>>(sql, args), scope, op);


public object dbSingle(TemplateScopeContext scope, string sql, Dictionary<string, object> args) =>
    exec(db => db.Single<Dictionary<string, object>>(sql, args), scope, null);

public object dbSingle(TemplateScopeContext scope, string sql, Dictionary<string, object> args, object op) =>
    exec(db => db.Single<Dictionary<string, object>>(sql, args), scope, op);

The overloads are so the filters can be called without specifying any filter options.

For more examples of different error handling features and strategies checkout: TemplateErrorHandlingTests.cs

made with by ServiceStack