Error Handling

Demis Bellot edited this page May 17, 2016 · 56 revisions

  1. Getting Started

    1. Creating your first project
      1. Create Service from scratch
    2. Your first webservice explained
    3. Example Projects Overview
    4. Learning Resources
  2. Designing APIs

    1. ServiceStack API Design
    2. Designing a REST-ful service with ServiceStack
      1. Simple Customer REST Example
    3. How to design a Message-Based API
    4. Software complexity and role of DTOs
  3. Reference

    1. Order of Operations
    2. The IoC container
    3. Configuration and AppSettings
    4. Metadata page
    5. Rest, SOAP & default endpoints
    6. SOAP support
    7. Routing
    8. Service return types
    9. Customize HTTP Responses
    10. Customize JSON Responses
    11. Plugins
    12. Validation
    13. Error Handling
    14. Security
    15. Debugging
    16. JavaScript Client Library (ss-utils.js)
  4. Clients

    1. Overview
    2. C#/.NET client
      1. .NET Core Clients
    3. Add ServiceStack Reference
      1. C# Add Reference
      2. F# Add Reference
      3. VB.NET Add Reference
      4. Swift Add Reference
      5. Java Add Reference
    4. Silverlight client
    5. JavaScript client
      1. Add TypeScript Reference
    6. Dart Client
    7. MQ Clients
  5. Formats

    1. Overview
    2. JSON/JSV and XML
    3. HTML5 Report Format
    4. CSV Format
    5. MessagePack Format
    6. ProtoBuf Format
  6. View Engines

    1. Razor & Markdown Razor
    2. Markdown Razor
  7. Hosts

    1. IIS
    2. Self-hosting
    3. Messaging
    4. Mono
  8. Security

    1. Authentication
    2. Sessions
    3. Restricting Services
    4. Encrypted Messaging
  9. Advanced

    1. Configuration options
    2. Access HTTP specific features in services
    3. Logging
    4. Serialization/deserialization
    5. Request/response filters
    6. Filter attributes
    7. Concurrency Model
    8. Built-in profiling
    9. Form Hijacking Prevention
    10. Auto-Mapping
    11. HTTP Utils
    12. Dump Utils
    13. Virtual File System
    14. Config API
    15. Physical Project Structure
    16. Modularizing Services
    17. MVC Integration
    18. ServiceStack Integration
    19. Embedded Native Desktop Apps
    20. Auto Batched Requests
    21. Versioning
    22. Multitenancy
  10. Caching

    1. Caching Providers
    2. HTTP Caching
      1. CacheResponse Attribute
      2. Cache Aware Clients
  11. Auto Query

    1. Overview
    2. Why Not OData
    3. AutoQuery RDBMS
    4. AutoQuery Data
      1. AutoQuery Memory
      2. AutoQuery Service
      3. AutoQuery DynamoDB
  12. Server Events

    1. Overview
    2. JavaScript Client
    3. C# Server Events Client
    4. Redis Server Events
  13. Service Gateway

    1. Overview
    2. Service Discovery
  14. Encrypted Messaging

    1. Overview
    2. Encrypted Client
  15. Plugins

    1. Auto Query
    2. Server Sent Events
    3. Swagger API
    4. Postman
    5. Request logger
    6. Sitemaps
    7. Cancellable Requests
    8. CorsFeature
  16. Tests

    1. Testing
    2. HowTo write unit/integration tests
  17. ServiceStackVS

    1. Install ServiceStackVS
    2. Add ServiceStack Reference
    3. TypeScript React Template
      1. React, Redux Chat App
    4. AngularJS App Template
    5. React Desktop Apps
  18. Other Languages

    1. FSharp
      1. Add ServiceStack Reference
    2. VB.NET
      1. Add ServiceStack Reference
    3. Swift
      1. Swift Add Reference
    4. Java
      1. Add ServiceStack Reference
      2. Android Studio & IntelliJ
      3. Eclipse
  19. Amazon Web Services

    1. ServiceStack.Aws
    2. PocoDynamo
    3. AWS Live Demos
    4. Getting Started with AWS
  20. Deployment

    1. Deploy Multiple Sites to single AWS Instance
      1. Simple Deployments to AWS with WebDeploy
    2. Advanced Deployments with OctopusDeploy
  21. Install 3rd Party Products

    1. Redis on Windows
    2. RabbitMQ on Windows
  22. Use Cases

    1. Single Page Apps
      1. HTML, CSS and JS Minifiers
    2. Azure
      1. Connecting to Azure Redis via SSL
    3. Logging
    4. Bundling and Minification
    5. NHibernate
  23. Performance

    1. Real world performance
  24. Other Products

    1. ServiceStack.Redis
    2. ServiceStack.OrmLite
    3. ServiceStack.Text
  25. Future

    1. Roadmap
Clone this wiki locally

Throwing C# Exceptions

In most cases you won't need to be concerned with ServiceStack's error handling since it provides native support for the normal use-case of throwing C# Exceptions, e.g.:

public object Post(User request) 
{
    if (string.IsNullOrEmpty(request.Name))
        throw new ArgumentNullException("Name");
}

Default Mapping of C# Exceptions to HTTP Errors

By Default C# Exceptions:

  • Inheriting from ArgumentException are returned with a HTTP StatusCode of 400 BadRequest
  • NotImplementedException or NotSupportedException is returned as a 405 MethodNotAllowed
  • AuthenticationException is returned as 401 Unauthorized
  • UnauthorizedAccessException is returned as 403 Forbidden
  • OptimisticConcurrencyException is returned as 409 Conflict
  • Other normal C# Exceptions are returned as 500 InternalServerError

This list can be extended with user-defined mappings on Config.MapExceptionToStatusCode.

WebServiceException

All Exceptions get injected into the ResponseStatus property of your Response DTO that is serialized into your ServiceClient's preferred Content-Type making error handling transparent regardless of your preferred format - i.e., the same C# Error handling code can be used for all ServiceClients.

try 
{
    var client = new JsonServiceClient(BaseUri);
    var response = client.Send<UserResponse>(new User());
} 
catch (WebServiceException webEx) 
{
    /*
      webEx.StatusCode        = 400
      webEx.StatusDescription = ArgumentNullException
      webEx.ErrorCode         = ArgumentNullException
      webEx.ErrorMessage      = Value cannot be null. Parameter name: Name
      webEx.StackTrace        = (your Server Exception StackTrace - in DebugMode)
      webEx.ResponseDto       = (your populated Response DTO)
      webEx.ResponseStatus    = (your populated Response Status DTO)
      webEx.GetFieldErrors()  = (individual errors for each field if any)
    */
}

Where the StatusCode and StatusDescription are the HTTP StatusCode and Description which shows the top-level HTTP-layer details that all HTTP Clients see. The StatusDescription is typically short and used to indicate the type of Error returned which by default is the Type of the Exception thrown. HTTP Clients normally inspect the StatusCode to determine how to handle the error on the client.

All Service Clients also have access to Application-level Error details which are returned in the Error Response DTO Body where the ErrorCode holds the Exception Type and is what clients would inspect to determine and handle the Type of Exception it is whilst the ErrorMessage holds the Server Exception Message which provides a human-friendly, longer and descriptive description of the Error that can be displayed to the end user. In DebugMode the StackTrace is populated with the Server StackTrace to help front-end developers from identifying the cause and location of the Error.

If the Error refers to a particular field such as a Field Validation Exception, GetFieldErrors() holds the error information for each field that has an Error.

These defaults can be changed to provide further customized error responses by the various options below:

Enabling StackTraces

By default displaying StackTraces in Response DTOs are only enabled in Debug builds, although this behavior is overridable with:

SetConfig(new HostConfig { DebugMode = true });

Error Response Types

The Error Response that gets returned when an Exception is thrown varies on whether a conventionally-named {RequestDto}Response DTO exists or not.

If it exists:

The {RequestDto}Response is returned, regardless of the service method's response type. If the {RequestDto}Response DTO has a ResponseStatus property, it is populated otherwise no ResponseStatus will be returned. (If you have decorated the {ResponseDto}Response class and properties with [DataContract]/[DataMember] attributes, then ResponseStatus also needs to be decorated, to get populated).

Otherwise, if it doesn't:

A generic ErrorResponse gets returned with a populated ResponseStatus property.

The Service Clients transparently handles the different Error Response types, and for schema-less formats like JSON/JSV/etc there's no actual visible difference between returning a ResponseStatus in a custom or generic ErrorResponse - as they both output the same response on the wire.

Custom Exceptions

Ultimately all ServiceStack WebServiceExceptions are just Response DTO's with a populated ResponseStatus that are returned with a HTTP Error Status. There are a number of different ways to customize how Exceptions are returned including:

Custom mapping of C# Exceptions to HTTP Error Status

You can change what HTTP Error Status is returned for different Exception Types by configuring them with:

SetConfig(new HostConfig { 
    MapExceptionToStatusCode = {
        { typeof(CustomInvalidRoleException), 403 },
        { typeof(CustomerNotFoundException), 404 },
    }
});

Returning a HttpError

If you want even finer grained control of your HTTP errors you can either throw or return an HttpError letting you customize the Http Headers and Status Code and HTTP Response body to get exactly what you want on the wire:

public object Get(User request) 
{
    throw HttpError.NotFound("User {0} does not exist".Fmt(request.Name));
}

The above returns a 404 NotFound StatusCode on the wire and is a short-hand for:

new HttpError(HttpStatusCode.NotFound, 
    "User {0} does not exist".Fmt(request.Name)); 

HttpError with a Custom Response DTO

The HttpError can also be used to return a more structured Error Response with:

var responseDto = new ErrorResponse { 
    ResponseStatus = new ResponseStatus {
        ErrorCode = typeof(ArgumentException).Name,
        Message = "Invalid Request",
        Errors = new List<ResponseError> {
            new ResponseError {
                ErrorCode = "NotEmpty",
                FieldName = "Company",
                Message = "'Company' should not be empty."
            }
        }
    }
};

throw new HttpError(HttpStatusCode.BadRequest, "ArgumentException") {
    Response = responseDto,
}; 

Implementing IResponseStatusConvertible

You can also override the serialization of Custom Exceptions by implementing the IResponseStatusConvertible interface to return your own populated ResponseStatus instead. This is how ValidationException allows customizing the Response DTO is by having ValidationException implement the IResponseStatusConvertible interface.

E.g. Here's a custom Exception example that returns a populated Field Error:

public class CustomFieldException : Exception, IResponseStatusConvertible
{
  ...
    public string FieldErrorCode { get; set; }
    public string FieldName { get; set; }
    public string FieldMessage { get; set; }

    public ResponseStatus ToResponseStatus()
    {
        return new ResponseStatus {
            ErrorCode = GetType().Name,
            Message = Message,
            Errors = new List<ResponseError> {
                new ResponseError {
                    ErrorCode = FieldErrorCode,
                    FieldName = FieldName,
                    Message = FieldMessage
                }
            }
        }
    }    
}

Implementing IHasStatusCode

In addition to customizing the HTTP Response Body of C# Exceptions with IResponseStatusConvertible, you can also customize the HTTP Status Code by implementing IHasStatusCode:

public class Custom401Exception : Exception, IHasStatusCode
{
    public int StatusCode 
    { 
        get { return 401; } 
    }
}

Likewise IHasStatusDescription can be used to customize the StatusDescription and IHasErrorCode for customizing the ErrorCode returned, instead of its Exception Type.

Overriding OnExceptionTypeFilter in your AppHost

You can also catch and modify the returned ResponseStatus returned by overriding OnExceptionTypeFilter in your AppHost, e.g. ServiceStack uses this to customize the returned ResponseStatus to automatically add a custom field error for ArgumentExceptions with the specified ParamName, e.g:

public virtual void OnExceptionTypeFilter(
    Exception ex, ResponseStatus responseStatus)
{
    var argEx = ex as ArgumentException;
    var isValidationSummaryEx = argEx is ValidationException;
    if (argEx != null && !isValidationSummaryEx && argEx.ParamName != null)
    {
        var paramMsgIndex = argEx.Message.LastIndexOf("Parameter name:");
        var errorMsg = paramMsgIndex > 0
            ? argEx.Message.Substring(0, paramMsgIndex)
            : argEx.Message;

        responseStatus.Errors.Add(new ResponseError
        {
            ErrorCode = ex.GetType().Name,
            FieldName = argEx.ParamName,
            Message = errorMsg,
        });
    }
}

Custom HTTP Errors

In Any Request or Response Filter you can short-circuit the Request Pipeline by emitting a Custom HTTP Response and Ending the request, e.g:

this.PreRequestFilters.Add((req,res) => 
{
    if (req.PathInfo.StartsWith("/admin") && 
        !req.GetSession().HasRole("Admin")) 
    {
        res.StatusCode = (int)HttpStatusCode.Forbidden;
        res.StatusDescription = "Requires Admin Role";
        res.EndRequest();
    }
});

To end the Request in a Custom HttpHandler use res.EndHttpHandlerRequest()

Fallback Error Pages

Use IAppHost.GlobalHtmlErrorHttpHandler for specifying a fallback HttpHandler for all error status codes, e.g.:

public override void Configure(Container container)
{
    this.GlobalHtmlErrorHttpHandler = new RazorHandler("/oops"),
}

For more fine-grained control, use IAppHost.CustomHttpHandlers for specifying custom HttpHandlers to use with specific error status codes, e.g.:

public override void Configure(Container container)
{
    this.CustomHttpHandlers[HttpStatusCode.NotFound] = 
        new RazorHandler("/notfound");
    this.CustomHttpHandlers[HttpStatusCode.Unauthorized] = 
        new RazorHandler("/login");
}

Register handlers for handling Service Exceptions

ServiceStack and its new API provides a flexible way to intercept exceptions. If you need a single entry point for all service exceptions, you can add a handler to AppHost.ServiceExceptionHandler in Configure. To handle exceptions occurring outside of services you can set the global AppHost.UncaughtExceptionHandlers handler, e.g.:

public override void Configure(Container container)
{
    //Handle Exceptions occurring in Services:

    this.ServiceExceptionHandlers.Add((httpReq, request, exception) => {
        //log your exceptions here
        ...
        return null; //continue with default Error Handling

        //or return your own custom response
        //return DtoUtils.CreateErrorResponse(request, exception);
    });

    //Handle Unhandled Exceptions occurring outside of Services
    //E.g. Exceptions during Request binding or in filters:
    this.UncaughtExceptionHandlers.Add((req, res, operationName, ex) => {
         res.Write("Error: {0}: {1}".Fmt(ex.GetType().Name, ex.Message));
         res.EndRequest(skipHeaders: true);
    });
}

Error handling using a custom ServiceRunner

If you want to provide different error handlers for different actions and services you can just tell ServiceStack to run your services in your own custom IServiceRunner and implement the HandleExcepion event hook in your AppHost:

public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(
    ActionContext actionContext)
{           
    return new MyServiceRunner<TRequest>(this, actionContext); 
}

Where MyServiceRunner is just a custom class implementing the custom hooks you're interested in, e.g.:

public class MyServiceRunner<T> : ServiceRunner<T> 
{
    public MyServiceRunner(IAppHost appHost, ActionContext actionContext) 
        : base(appHost, actionContext) {}

    public override object HandleException(IRequest request, 
        T request, Exception ex) {
      // Called whenever an exception is thrown in your Services Action
    }
}

Community Resources