5

I am new to ASP.NET web api and I was reading these two article about exception handling

  1. Exception handling
  2. Error handling

While I understand Exception handling link, I am not sure about using Error handling. Here is what I want to do:

  1. when an error occurs, I want to log that error with message, stack trace & request object that was sent with the original request.
  2. The type of every incoming request can be different & I am planning to serialize the request object into JSON string and store them in DB while error logging.

It seems like I should add try catch block on every method and then perform the exception handling and logging. This will be a tedious task. Is there any way to globally handle error and still able to capture exceptions details (message, stack trace) & log request object.

2
  • 1
    read up on the cross-cutting concerns in web api. You controller actions should be as lean as possible. Commented Dec 27, 2016 at 15:36
  • 1
    What about the Error Handling link did you not understand? That article answers this question... Commented Dec 27, 2016 at 15:42

2 Answers 2

6

Is there any way to globally handle error and still able to capture exceptions details (message, stack trace) & log request object.

Yes, ASP.NET Web API 2.1 have framework support for global handling of unhandled exceptions, instead of adding try catch block on every method.

It allows use to customize the HTTP response that is sent when an unhandled application exception occurs.

WebApiConfig

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // ...

        config.Services.Replace(typeof (IExceptionHandler), 
            new GlobalExceptionHandler());
    }
}

GlobalExceptionHandler

public class GlobalExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        var exception = context.Exception;

        var httpException = exception as HttpException;
        if (httpException != null)
        {
            context.Result = new CustomErrorResult(context.Request,
                (HttpStatusCode) httpException.GetHttpCode(), 
                 httpException.Message);
            return;
        }

        // Return HttpStatusCode for other types of exception.

        context.Result = new CustomErrorResult(context.Request, 
            HttpStatusCode.InternalServerError,
            exception.Message);
    }
}

CustomErrorResult

public class CustomErrorResult : IHttpActionResult
{
    private readonly string _errorMessage;
    private readonly HttpRequestMessage _requestMessage;
    private readonly HttpStatusCode _statusCode;

    public CustomErrorResult(HttpRequestMessage requestMessage, 
       HttpStatusCode statusCode, string errorMessage)
    {
        _requestMessage = requestMessage;
        _statusCode = statusCode;
        _errorMessage = errorMessage;
    }

    public Task<HttpResponseMessage> ExecuteAsync(
       CancellationToken cancellationToken)
    {
        return Task.FromResult(_requestMessage.CreateErrorResponse(
            _statusCode, _errorMessage));
    }
}

Credit to ASP.NET Web API 2: Building a REST Service from Start to Finish

Sign up to request clarification or add additional context in comments.

Comments

0

Create a filter to handle those operations for you and then register the filter globally. We do something very similar, here's the filter class we use.

public class FailedApiRequestLoggerAttribute : ActionFilterAttribute
{
    private readonly bool _removeErrorDetailsFromResponse;

    public FailedApiRequestLoggerAttribute(bool removeErrorDetailsFromResponse)
    { _removeErrorDetailsFromResponse = removeErrorDetailsFromResponse; }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        base.OnActionExecuted(actionExecutedContext);

        var log = LoggerFactory.GetLogger(actionExecutedContext.ActionContext.ControllerContext.Controller.GetType().Name);

        // If there is no response object then we're probably here because an exception was 
        // thrown and thrown exceptions are handled elsewhere.
        if (actionExecutedContext.Response?.IsSuccessStatusCode == false)
        {
            var error = new StringBuilder();
            error.AppendLine("API Call Returned Non-Success Status");
            error.AppendLine($"{actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName}.{actionExecutedContext.ActionContext.ActionDescriptor.ActionName}");

            if (actionExecutedContext.ActionContext.ActionArguments.Any())
            { error.AppendLine($"  Arguments"); }

            foreach (var argument in actionExecutedContext.ActionContext.ActionArguments)
            { error.AppendLine($"    {JsonConvert.SerializeObject(argument)}"); }

            error.AppendLine("  Response");
            error.AppendLine($"    Status Code: {actionExecutedContext.Response.StatusCode}; Reason: {actionExecutedContext.Response.ReasonPhrase}");

            var content = actionExecutedContext.Response.Content as ObjectContent<HttpError>;
            if (content != null)
            {
                error.AppendLine($"    {JsonConvert.SerializeObject(content.Value)}");

                if (_removeErrorDetailsFromResponse)
                { ((HttpError)content.Value).Clear(); }
            }

            log.Warning(error.ToString());
        }
    }
}

And then it gets registered globally.

config.Filters.Add(new FailedApiRequestLoggerAttribute(true));

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.