Looking at: https://github.com/ServiceStack/ServiceStack/wiki/Error-Handling#webserviceexception

It appears that this documentation might be out of date with what we are seeing in v.56.

Calling a service with JsonServiceClient, and we implement the service like this to throw an InvalidOperationException:

    public class GetThrowerDto : IReturn<GetThrowerDtoResponse>
        public string ExceptionType { get; set; }

    public class GetThrowerDtoResponse
        public ResponseStatus ResponseStatus { get; set; }


    [Route("/testing/thrower", "GET")]
    public object Get(GetThrowerDto request)
        throw new InvalidOperationException("amessage");

inspecting the WebServiceException created by a JsonServiceClient
If we then throw InvalidOperationException, it looks like this:

StatusCode: 500
ErrorCode: "InvalidOperationException"
Message: "InvalidOperationException"

ErrorMessage: "amessage"
StatusDescription: "InvalidOperationException"

If we then throw ArgumentOutOfRangeException, it looks like this:

StatusCode: 400
ErrorCode: "ArgumentOutOfRangeException"
Message: "ArgumentOutOfRangeException"

ErrorMessage: "Specified argument was out of the range of valid values.\r\nParameter name: amessage"
StatusDescription: "ArgumentOutOfRangeException"

Now, docs aside, if I wanted to sanitize/obfuscate the information about exceptions that are thrown from my service, I would not want their exact .NET type disclosed (it is irrelevant and sensitive information)
I might want to alter the values of the ErrorCode, Message, StatusDescription to be something more generic.

How would I do that?

Does not seem that I can alter the StatusDescription or Message values by overriding the ‘AppHost.OnExceptionTypeFilter’ method: https://github.com/ServiceStack/ServiceStack/wiki/Error-Handling#overriding-onexceptiontypefilter-in-your-apphost.

I can alter the ErrorCode and ErrorMessage, but the StatusDescription and Message properties still describe the .NET type of the original thrown exception.

The Exception Type is important it’s tells the client what Exception it is and it only populates the StackTrace in DebugMode. It’s expected for the client to inspect the ErrorCode/Exception Type to workout how to handle the Error appropriately in the client application.

If it’s a custom exception you can implement IHasStatusDescription to change the StatusDescription that gets returned. Otherwise if you use register a ServiceExceptionHandlers and change what Exception gets returned, e.g. you could return a custom HttpError with StatusCode/Description populated. Otherwise since ServiceExceptionHandlers accepts a IRequest you can short-circuit the response with something like:

req.Response.StatusCode = 400;
req.Response.StatusDescription= "There was an Error";

Otherwise you can always implement a custom ServiceRunner and override HandleException() to also return a custom HttpError.

Justification all sounds fair.

So, if I didn’t want to expose the exact exception type being thrown from deep in the bowls of my service, and I wanted instead to describe those exceptions as a general System.Exception in the ErrorCode, then I should do that by overriding: ServiceExceptionHandlers and provide my own explanation for the client??

Is that right?
I don’t think I want to do my own ServiceRunner in general, eh?

You would just register a callback in ServiceExceptionHandlers or you could override AppHost.OnServiceException(). Using a custom ServiceRunner is another option if you prefer it, can’t think of any disadvantages of using it.

Not sure what you plan to change it to or how you’re going to distinguish different Exceptions, maybe you’re just planning on showing a generic “There was an Error” message on the client.

Intention is to only expose certain kinds of exceptions to the client.

We use the MapExceptionToStatusCode for those:

            MapExceptionToStatusCode =
                { typeof(ValidationError), 400 },
                { typeof(ArgumentException), 400 },
                { typeof(ArgumentNullException), 400 },
                { typeof(ArgumentOutOfRangeException), 400 },
                { typeof(RuleViolationException), 400 },
                { typeof(AuthenticationException), 401 },
                { typeof(UnauthorizedAccessException), 401 },
                { typeof(RoleViolationException), 403 },
                { typeof(ForbiddenException), 403 },
                { typeof(ResourceNotFoundException), 404 },
                { typeof(ResourceConflictException), 409 },

Happy for the client to see the type of these exceptions, but not others.

For any other exception types we want to just throw a general System.Exception with a generic “Unexpected Error” type of message to the client, with a CorrelationID. Rather than the actual exception type (which could be anything, exposing the internals of our service). The client simply does not need to know the exact type. It will need to assume its an unrecoverable exception from the server (since its a 500 - InternalServerError).

1 Like

We achieved what we wanted by providing a custom ServiceExceptionHandler:

ServiceExceptionHandlers.Add((httpRequest, request, ex) =>
            if (IsUnexpectedException(ex))
                var exception = new Exception(string.Empty, new UnexpectedException(ex));
                return DtoUtils.CreateErrorResponse(request, exception);

            return DtoUtils.CreateErrorResponse(request, ex);})

[Note: we have to double wrap the actually thrown exception, because DtoUtils.CreateErrorResponse() unwraps the outer exception and uses the inner]

For us, this all means that we have made exception types that we don’t want exposed to the client, appear to be general exceptions with our own message "Unexpected Error".
We determined those exceptions as any not in the MapExceptionToStatusCode list

I still don’t have a clear understanding of the intention of the various values in the WebServiceException, and how they are designed to be used by clients:

  • Message
  • ErrorCode
  • ErrorMessage
  • StatusDescription

It would be nice if the doco made that really clear to us, so that we can know what to control and what is better left to default behavior.

The ErrorCode is usually the Exception Type and usually what your code looks at to determine the type of Exception that it is.

The Message is there since WebServiceException inherits Exception, it’s populated from the StatusDescription (since it needs it before it tries to deserialie the ErrorResponse DTO). The StatusDescription is usually the ErrorCode but can be customized differently. The ErrorMessage is the Server Exception Message.

So ErrorCode / StatusDescription is usually the same however sometimes you want a friendlier StatusDescription than the Exception Type as this is the top-level error all HTTP Clients see. The ErrorCode is what your code would inspect to determine and handle the Error type, it defaults to the Exception Type but you can also have exceptions implement IHasErrorCode if you want finer-grained Error codes than just the Exception Type. The Error Message is the user-defined Server Exception Message, this can be long and descriptive and contain any chars.

In short the ErrorCode / ErrorMessage come from the ResponseStatus DTO. The StatusDescription is from the HTTP Response StatusDescription whilst you can ignore the Message which is just the StatusDescription.

Thanks Mythz,

Can we update the doco with this gem? and info at start of thread.
I am happy to do that if you need manpower

I had similar goals to @jezzsantos, I didn’t want to expose certain kinds of exception from my “public” API. I only let HttpError, and custom exceptions bubble to the client. All other types I presume to be programming or internal errors and want to hide these. Anyway, I found ResolveResponseException to be a good hook for wrapping/replacing the original exception with one that the client will see.

public override Exception ResolveResponseException(Exception ex)
    if (IsExposableException(ex))
        return ex;

    return new HttpError((int)HttpStatusCode.InternalServerError, "InternalError", "The server encountered an internal error. Please retry the request.", ex);
1 Like