Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elmah doesn't log exceptions using WebAPI with HttpResponseException

In my WebApi code, I raise a HttpResponseException which short-circuits the request pipeline and generates a valid Http response. However, I'm trying to integrate webApi with elmah logging, yet the HttpResponseExeptions aren't showing up.

I have the web.config set-up for elmah and have the following code:

In Global.asx.cs:

static void ConfigureWebApi(HttpConfiguration config)
{  
    config.Filters.Add(new ServiceLayerExceptionFilter());            
    config.Filters.Add(new ElmahHandledErrorLoggerFilter());
    config.DependencyResolver = new WebApiDependencyResolver(ObjectFactory.Container);                            
}     

Filter:

public class ElmahHandledErrorLoggerFilter : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        base.OnException(actionExecutedContext);
        ErrorSignal.FromCurrentContext().Raise(actionExecutedContext.Exception);
    }
}

Code where exception is raised:

public Task<FileUpModel> UploadFile()
{
    if (Request.Content.IsMimeMultipartContent())
    {                
        var provider = new TolMobileFormDataStreamProvider("C:\images\");

        var task = Request.Content.ReadAsMultipartAsync(provider).ContinueWith(
        t =>
        {

            if (t.IsFaulted || t.IsCanceled)
                throw new HttpResponseException(HttpStatusCode.InternalServerError);

            var fileInfo = provider.FileData.FirstOrDefault();
            if (fileInfo == null)
                // the exception here isn't logged by Elmah?!
                throw new HttpResponseException(HttpStatusCode.InternalServerError);    

            var uploadModel = new FileUpModel { success = true };
            return uploadModel;
        });

        return task;
    }
    else
    {
        throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
    }            
}   

Can anyone who has implemented this before let me know what I'm doing wrong?

like image 718
jaffa Avatar asked Jun 03 '13 19:06

jaffa


1 Answers

As mentioned above, the Elmah filter does not catch and log anything when you raise a HttpResponseException. More specifically, if the following syntax is used:

return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "It was a bad request");
or
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "HttpResponseException - This request is not properly formatted"));

I wanted to trap and log an error in both cases. The way to do it is to use an "ActionFilterAttribute", override "OnActionExecuted", and check actionExecutedContext.Response.IsSuccessStatusCode.

public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
    // when actionExecutedContext.Response is null, the error will be caught and logged by the Elmah filter
    if ((actionExecutedContext.Response != null) && !actionExecutedContext.Response.IsSuccessStatusCode)
    {
        try
        {
            var messages = (System.Web.Http.HttpError)((System.Net.Http.ObjectContent<System.Web.Http.HttpError>)actionExecutedContext.Response.Content).Value;
            StringBuilder stringBuilder = new StringBuilder();
            foreach (var keyValuePair in messages) {
                stringBuilder.AppendLine("Message: Key - " + keyValuePair.Key + ", Value - " + keyValuePair.Value); 
            }
            Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception("Web API Failed Status Code returned - " + stringBuilder.ToString()));
        }
        catch (Exception ex)
        {
            Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception("Error in OnActionExecuted - " + ex.ToString()));
        }
    }
}

On a side note, I also overwrote "OnActionExecuting" to validate the model state. This allowed me to remove all of the checks within my actions.

public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
    if (actionContext.ModelState != null && !actionContext.ModelState.IsValid)
    {
        StringBuilder stringBuilder = new StringBuilder();
        foreach (var obj in actionContext.ModelState.Values)
        {
            foreach (var error in obj.Errors)
            {
                if(!string.IsNullOrEmpty(error.ErrorMessage)) {
                    stringBuilder.AppendLine("Error: " + error.ErrorMessage);
                }
            }
        }
        Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception("Invalid Model State -- " + stringBuilder.ToString()));
        actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
    }
}

Of course, you will need to add the filter using "config.Filters.Add".

like image 76
victordscott Avatar answered Oct 16 '22 10:10

victordscott