In line with the ServiceStack documentation, we have a global service exception handler. The docs say that this handler should log the exception then call DtoUtils.HandleException, like this:
private object LogServiceException(object request, Exception exception)
{
var message = string.Format("Here we make a custom message...");
_logger.Error(message, exception);
return DtoUtils.HandleException(this, request, exception);
}
This results in the error being logged twice, since DTOUtils.HandleException also logs it, in a less customised format. Yes, I much prefer this to the DTOUtils logging and don't want to just use that.
How do we turn off DTOUtils logging while retaining the rest of the functionality? Nobody likes getting twice as many error emails as they should.
I hope the following code solves your problem.
based on the documentation New API, Custom Hooks, ServiceRunner
and Fine grain error handling using the New API's ServiceRunner
in AppHost.Configure
LogManager.LogFactory = new ServiceStack.Logging.Support.Logging.ConsoleLogFactory();
then in AppHost class
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new MyServiceRunner<TRequest>(this, actionContext);
}
in the ServiceRunner class
public class MyServiceRunner<T> : ServiceRunner<T>
{
public override object HandleException(IRequestContext requestContext, T request, Exception ex)
{
if ( isYourCondition )
{
ResponseStatus rs = new ResponseStatus("error1", "your_message");
// optionally you can add custom response errors
rs.Errors = new List<ResponseError>();
rs.Errors.Add(new ResponseError());
rs.Errors[0].ErrorCode = "more details 2";
// create an ErrorResponse with the ResponseStatus as parameter
var errorResponse = DtoUtils.CreateErrorResponse(request, ex, rs);
// log the error
Log.Error("your_message", ex);
return errorResponse;
}
else
return base.HandleException(requestContext, request, ex);
}
}
if you return the base.HandleException, it calls internally the DtoUtils.HandleException.
You will see in console, one log error only.
In client, if you handle the WebServiceException for custom errors.
catch (WebServiceException err)
{
if ( err.ResponseStatus.Errors != null)
{ // do something with err.ResponseStatus.Errors[0].ErrorCode;
}
}
Do not call DtoUtils.HandleException as it logs the error. Don't call ServiceRunner.HandleException either, it calls DtoUtils.HandleException.
Call DtoUtils.CreateErrorResponse to make the response (it is used by DtoUtils.HandleException). The ToResponseStatus helper is also in DtoUtils
My AppServiceRunner is now like this:
public class AppServiceRunner<T> : ServiceRunner<T>
{
public AppServiceRunner(AppHost appHost, ActionContext actionContext)
: base(appHost, actionContext)
{
}
public override object HandleException(IRequestContext requestContext,
T request, Exception ex)
{
LogException(requestContext, request, ex);
var responseStatus = ex.ToResponseStatus();
return DtoUtils.CreateErrorResponse(request, ex, responseStatus);
}
private void LogException(IRequestContext requestContext, T request, Exception ex)
{
// since AppHost.CreateServiceRunner can be called before AppHost.Configure
// don't get the logger in the constructor, only make it when it is needed
var logger = MakeLogger();
var requestType = typeof(T);
var message = string.Format("Exception at URI:'{0}' on service {1} : {2}",
requestContext.AbsoluteUri, requestType.Name, request.ToJson());
logger.Error(message, ex);
}
private static ILog MakeLogger()
{
return LogManager.GetLogger(typeof(AppServiceRunner<T>));
}
}
Now the only service errors that I get are those generated by this code.
Related
When I catch an exception in a controller, the client side still receives an InternalServerError, how can I resolve this?
I have my own 'Result' class, which I can set to failed, and populate with a user friendly error message, I want to catch exceptions in specific controller methods, and send back my failed 'Result' class instead of the code 500 InternalServerError.
Thanks for the help!
[HttpGet]
[Route(Server_DevTestApiEndpoints.INTERNAL_SERVER_EXCEPTION_RESULT_TEST)]
public Result GetInternalServerExceptionTest()
{
try
{
throw new Exception();
return new Result();
}
catch (Exception e)
{
return new Result(e);
}
}
It will be better from architectural standpoint to use an ExceptionFilterAttribute, that will be responsible for catching all errors in your controller actions:
public class LogExceptionFilterAttribute : ExceptionFilterAttribute
{
public override async Task OnExceptionAsync(ExceptionContext context)
{
//log your exception;
context.Result = new ObjectResult("your custom message")
{
StatusCode = StatusCodes.Status200OK
};
}
}
and just decorate your action or entire controller:
[HttpGet]
[LogExceptionFilter]
[Route(Server_DevTestApiEndpoints.INTERNAL_SERVER_EXCEPTION_RESULT_TEST)]
public Result GetInternalServerExceptionTest()
{
//your action logic
}
I'm working on a WebAPI 2 project and I've implemented a global error filter attribute which looks like this:
public class MyExceptionFilterAttribute: ExceptionFilterAttribute
{
public override void OnException(HttpActionExecuteContext context)
{
if (context.Exception is MyException)
{
// Handle MyException
}
else if (context.Exception is UnauthorizedAccessException)
{
// Handle UnauthorizedAccessException
}
}
}
When I make calls to an external API and get error responses back I throw MyException.
var httpResponseMessage = _httpClient.GetAsync(request).Result;
if (!httpResponseMessage.IsSuccessStatusCode)
{
var message = "Error calling external API";
var except = new MyException(message);
throw except;
}
I want to pass the status code that the API returned to the global error filter as part of MyException. My first attempt was to add the status code as a property of MyException.
public class MyException : Exception
{
public HttpStatusCode statusCode;
public MyException() : base() { }
public MyException(string message, HttpStatusCode statusCode) : base(message)
{
this.statusCode = statusCode;
}
}
And in the exception throwing:
var httpResponseMessage = _httpClient.GetAsync(request).Result;
if (!httpResponseMessage.IsSuccessStatusCode)
{
var message = "Error calling external API";
var except = new MyException(message, httpResponseMessage.StatusCode);
throw except;
}
Then in my filter I was casting context.Exception back to a MyException and trying to access the property, but the property always came back as null.
var ex = context.Exception as MyException;
var code = ex.statusCode; // Always comes back as null
My second attempt was to add the status code to the Data property that MyException inherits from Exception.
var httpResponseMessage = _httpClient.GetAsync(request).Result;
if (!httpResponseMessage.IsSuccessStatusCode)
{
var message = "Error calling external API";
var except = new MyException(message);
except.Data.add("status", httpResponseMessage.StatusCode);
throw except;
}
However, when I tried to access it in the filter, Data had no keys. I know that I could include the status code in my exception message and then parse it out in the filter, but I would rather not do that because I think it's the wrong solution. How can I get the status code to my exception filter so that I can use it when handling my exceptions?
The exception may be getting wrapped higher up in the call stack. It will then be in an inner exception. You could iterate all of the inner exceptions to see if any of them are your exception, but your code to access the properties should work as expected.
var ex = context.Exception;
while (ex != null)
{
if (ex is MyException)
{
// Do work
break;
}
ex = ex.InnerException;
}
In my C# Web API, I'm trying to add a global exception handler. I've been using a custom global ExceptionFilterAttribute to handle the exception and return a HttpResponseMessage:
public override void OnException(HttpActionExecutedContext context)
{
...
const string message = "An unhandled exception was raised by the Web API.";
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(message),
ReasonPhrase = message
};
context.Response = httpResponseMessage;
}
This has worked fine for handling exceptions thrown at the controller level.
However, during development we had an error thrown from our OWIN startup file due to a database connection issue, however, a standard IIS exception was returned, instead of going through the global exception handler, and the full HTML was returned to our API consumer.
I've tried a few different approaches to catch exceptions thrown in my OWIN startup:
Custom ApiControllerActionInvoker:
public class CustomActionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var result = base.InvokeActionAsync(actionContext, cancellationToken);
if (result.Exception != null && result.Exception.GetBaseException() != null)
{
...
}
return result;
}
}
Custom ExceptionHandler:
public class CustomExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
...
base.Handle(context);
}
public override bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
}
Custom OwinMiddleware component:
public class CustomExceptionMiddleware : OwinMiddleware
{
public CustomExceptionMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{
...
}
}
}
And finally just using Application_Error:
protected void Application_Error(object sender, EventArgs e)
{
...
}
But nothing seems to catch the exception.
Does anyone know of a way to catch the exception and return a HttpResponseMessage? Or if any of the approaches I've already tried should have worked?
Any help much appreciated.
I have an application that does this correctly. In my case I wrote a middleware class that always returns a message telling the caller that the service is unavailable because there was an error during startup. This class is called FailedSetupMiddleware in my solution. The outline of it looks like this:
public class FailedSetupMiddleware
{
private readonly Exception _exception;
public FailedSetupMiddleware(Exception exception)
{
_exception = exception;
}
public Task Invoke(IOwinContext context, Func<Task> next)
{
var message = ""; // construct your message here
return context.Response.WriteAsync(message);
}
}
In my Configuration class I have a try...catch block that configures the OWIN pipeline with only the FailedSetupMiddleware in the case where an exception was thrown during configuration.
My OWIN startup class looks like this:
[assembly: OwinStartup(typeof(Startup))]
public class Startup
{
public void Configuration(IAppBuilder app)
{
try
{
//
// various app.Use() statements here to configure
// OWIN middleware
//
}
catch (Exception ex)
{
app.Use(new FailedSetupMiddleware(ex).Invoke);
}
}
}
Using Web API, I have an exception filter that is supposed to write a custom payload using data from certain exceptions. This works fine.
However, I'm also using an action filter that is supposed to add some HTTP headers to the response message. This works fine when no exception is thrown.
When an exception is thrown, though, my action filter is given a NULL Response (HttpActionExecuted.Response), and can therefore not add its headers. I've attempted to solve this by creating a new ResponseMessage before adding the headers if the response is NULL. But when I create a new ResponseMessage in the action filter, my exception filter is no longer invoked.
I guess the Web API model somehow figures no exception was thrown if a response message exists. Any ideas to how I can use an exception filter, while still having an action filter adding HTTP headers?
My exception filter:
public class ExceptionStatusFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (!(context.Exception is CertainException))
{
return;
}
var exception = context.Exception as CertainException;
var error = new DefaultErrorContainer
{
Error = new Error
{
Description = exception.Message,
ErrorCode = exception.ErrorCode,
ErrorThirdParty = exception.ThirdPartyErrorCode
}
};
context.Response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, error);
}
}
And the action filter:
public class ProfilingActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext context)
{
var header = "foo";
if (context.Response == null)
{
context.Response = new HttpResponseMessage();
}
context.Response.Headers.Add("X-SOME-HEADER", header);
}
}
From this SO link you can get to know the order in which Action filters are getting called.
Exception filters are last ones to get called, so if you are generating a response or handling the exception before this filter gets executes, your Exception filter will assume no exception is thrown and it wont execute.
My suggestion would be on exception, create a response and attach header separately to this exception response as well.
This link can also help.
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
// Append Header here.
context.Response.Headers.Add("X-SOME-HEADER", header);
}
}
How comes that a custom ExceptionHandler is never called and instead a standard response (not the one I want) is returned?
Registered like this
config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
and implemented like this
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ExceptionResponse
{
statusCode = context.Exception is SecurityException ? HttpStatusCode.Unauthorized : HttpStatusCode.InternalServerError,
message = "An internal exception occurred. We'll take care of it.",
request = context.Request
};
}
}
public class ExceptionResponse : IHttpActionResult
{
public HttpStatusCode statusCode { get; set; }
public string message { get; set; }
public HttpRequestMessage request { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(statusCode);
response.RequestMessage = request;
response.Content = new StringContent(message);
return Task.FromResult(response);
}
}
and thrown like this (test)
throw new NullReferenceException("testerror");
in a controller or in a repository.
UPDATE
I do not have another ExceptionFilter.
I found a trigger for this behavior:
Given URL
GET http://localhost:XXXXX/template/lock/someId
sending this header, my ExceptionHandler works
Host: localhost:XXXXX
sending this header, it doesn't work and the built-in handler returns the error instead
Host: localhost:XXXXX
Origin: http://localhost:YYYY
This might be an issue with CORS requests (I use the WebAPI CORS package globally with wildcards) or eventually my ELMAH logger. It also happens when hosted on Azure (Websites), though the built-in error handler is different.
Any idea how to fix this?
Turns out the default only handles outermost exceptions, not exceptions in repository classes. So below has to be overridden as well:
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return context.ExceptionContext.IsOutermostCatchBlock;
}
UPDATE 1
WebAPI v2 does not use IsOutermostCatchBlock anymore. Anyway nothing changes in my implementation, since the new code in ShouldHandle still prevents my Error Handler. So I'm using this and my Error Handler gets called once. I catch errors in Controllers and Repositories this way.
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
UPDATE 2
Since this question got so much attention, please be aware that the current solution is the one linked by #JustAMartin in the comments below.
The real culprit here is CorsMessageHandler inserted by EnableCors method in message processing pipline. The catch block intercept any exception and convert into a response before it can reach the HTTPServer try-catch block and ExceptionHandler logic can be invoked
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
CorsRequestContext corsRequestContext = request.GetCorsRequestContext();
HttpResponseMessage result;
if (corsRequestContext != null)
{
try
{
if (corsRequestContext.IsPreflight)
{
result = await this.HandleCorsPreflightRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
result = await this.HandleCorsRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
catch (Exception exception)
{
result = CorsMessageHandler.HandleException(request, exception);
return result;
}
}
result = await this.<>n__FabricatedMethod3(request, cancellationToken);
return result;
}