Error Handling

#Script 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 method 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 method:

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

Unhandled Exceptions Behavior

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

new ScriptContext {
    SkipExecutingFiltersIfError = true
}

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

ifError Only execute the expression 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 method:
{{ 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 methods when Exceptions occur.

Controlling Unhandled Exception Behavior

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

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

Continue Executing Methods on Unhandled Exceptions

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

Accessing Page Exceptions

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

Throwing Exceptions

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

Method 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 methods, see the Error Handling Methods for examples.

These methods will only throw if a condition is met:

Method 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* methods 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* methods 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 method 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 template expressions are rendered where if preferred exceptions can be rendered in-line in the template expression where they occured with:

var context = new ScriptContext {
    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 template expression to null which by default will suppress the following naked Exceptions thrown in methods:

Implementing Method Exceptions

In order for your own Method Exceptions to participate in the above Script Error Handling they'll need to be wrapped in an StopFilterExecutionException including both the Script'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 methods is to call a managed function which catches all Exceptions and throws them in a StopFilterExecutionException as seen in OrmLite's DbScripts:

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

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

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


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

public object dbSingle(ScriptScopeContext 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 methods can be called without specifying any method options.

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

made with by ServiceStack