-
Notifications
You must be signed in to change notification settings - Fork 0
Error Handling
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");
}
By Default C# Exceptions:
- Inheriting from
ArgumentException
are returned as a HTTP StatusCode of 400 BadRequest -
NotImplementedException
is returned as a 405 MethodNotAllowed -
UnauthorizedAccessException
is returned as 403 Forbidden - Other normal C# Exceptions are returned as 500 InternalServerError
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.ErrorCode = ArgumentNullException
webEx.Message = Value cannot be null. Parameter name: Name
webEx.StackTrace = (your Server Exception StackTrace - if DebugMode is enabled)
webEx.ResponseDto = (your populated Response DTO)
webEx.ResponseStatus = (your populated Response Status DTO)
webEx.GetFieldErrors() = (individual errors for each field if any)
*/
}
By default displaying StackTraces in Response DTOs are only enabled in Debug builds, although this behavior is overridable with:
SetConfig(new EndpointHostConfig { DebugMode = true });
The Error Response that gets returned when an Exception is thrown varies on whether a conventionally-named {RequestDto}Response
DTO exists or not.
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.
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.
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(requst.Name));
}
the above is a short-hand for new HttpError(HttpStatusCode.NotFound, string.Format("User {0} does not exist", request.Name))
which returns a 404 NotFound StatusCode on the wire.
In the end all Web Service Exceptions are simply serialized into a ResponseStatus DTO which by default, is serialized by convention.
In addition to the above options, you can override the serialization of ad-hoc exceptions by implementing the IResponseStatusConvertible.ToResponseStatus()
method and have it return your own populated ResponseStatus instance instead.
Use Config.MapExceptionToStatusCode
to change what error codes are returned for different exceptions, e.g.:
SetConfig(new EndpointHostConfig {
MapExceptionToStatusCode = {
{ typeof(CustomInvalidRoleException), 403 },
{ typeof(CustomerNotFoundException), 404 },
}
});
Use Config.CustomHttpHandlers
for specifying custom HttpHandlers to use with specific error status codes, e.g.:
SetConfig(new EndpointHostConfig {
CustomHttpHandlers = {
{ HttpStatusCode.NotFound, new RazorHandler("/notfound") },
{ HttpStatusCode.Unauthorized, new RazorHandler("/login") },
}
});
Use Config.GlobalHtmlErrorHttpHandler
for specifying a fallback HttpHandler for all error status codes, e.g.:
SetConfig(new EndpointHostConfig {
GlobalHtmlErrorHttpHandler = new RazorHandler("/oops"),
});
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 AppHostBase.ServiceExceptionHandler
in Configure
. To handle exceptions occurring outside of services you can set the global AppHostBase.ExceptionHandler
handler, e.g.:
public override void Configure(Container container)
{
//Handle Exceptions occurring in Services:
this.ServiceExceptionHandler = (request, exception) => {
//log your exceptions here
...
//call default exception handler or prepare your own custom response
return DtoUtils.HandleException(this, request, exception);
};
//Handle Unhandled Exceptions occurring outside of Services
//E.g. in Request binding or filters:
this.ExceptionHandler = (req, res, operationName, ex) => {
res.Write("Error: {0}: {1}".Fmt(ex.GetType().Name, ex.Message));
res.EndServiceStackRequest(skipHeaders: true);
};
}
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(IRequestContext requestContext,
T request, Exception ex) {
// Called whenever an exception is thrown in your Services Action
}
}
If you have old services you still can handle all exceptions in one single place. Create a base class for your services and override HandleException
method like shown below.
public class MyServiceBase<TRequest> : RestService<TRequest>
{
public override object HandleException(TRequest request, Exception exception)
{
//log your exceptions here
...
//call default exception handler or prepare your own custom response with
return base.HandleException(request, exception);
// or prepare new customer response with new HttpError(...)
}
}
- Why ServiceStack?
- What is a message based web service?
- Advantages of message based web services
- Why remote services should use separate DTOs
- Getting Started
- Reference
- Clients
- Formats
- View Engines 4. Razor & Markdown Razor
- Hosts
- Advanced
- Configuration options
- Access HTTP specific features in services
- Logging
- Serialization/deserialization
- Request/response filters
- Filter attributes
- Concurrency Model
- Built-in caching options
- Built-in profiling
- Messaging and Redis
- Form Hijacking Prevention
- Auto-Mapping
- HTTP Utils
- Virtual File System
- Config API
- Physical Project Structure
- Modularizing Services
- Plugins
- Tests
- Other Languages
- Use Cases
- Performance
- How To
- Future