Search code examples
c#asp.netasp.net-web-apiodata

Catch/translate exception from ASP.NET OData Get method


I have an OData service implemented, using ASP.NET OData library.

So, I have a controller like this one:

public class ProjectsController : ODataController
{
    private readonly MyContext _db;

    public ProjectsController(MyContext db)
    {
        _db = db;
    }

    [EnableQuery]
    public IQueryable<Project> Get(string customQuery)
    {
        var query = _db.Projects;

        if (!string.IsNullOrWhitespace(customQuery))
        {
            query = query.Where(/* something complex going here */);
        }

        return query.OrderByDescending(p => p.Id);
    }

}

Now, all this works fine. But, in some cases, some specific "customQuery" may produce SQL code which results in division by zero. And, as result of that, the server sends back status 500 (oops) and the error object like this one:

{"error":{"code":"","message":"An error has occurred."}}

This is not very informative. I want to catch the exception and translate it 400 with some meaningful message (advising the user how to fix the custom query).

I've tried setting global exception filter, attribute exception filter.. no luck. Any ideas?


Solution

  • In case anyone is interested the way to go was to implement my own IExceptionHandler:

    class MyExceptionHandler : IExceptionHandler
    {
        public virtual Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
        {
            if (context.Request.Method == HttpMethod.Get && context.Request.RequestUri.AbsolutePath == "/odata/projects")
            {
                if (IsDivideByZero(context.Exception))
                {
                    const string message = "Division by zero encountered while applying the filter";
                    var response = context.Request.CreateResponse(HttpStatusCode.BadRequest, new HttpError(message));
                    context.Result = new ResponseMessageResult(response);
                }
            }
    
            return Task.CompletedTask;
        }
    
        private static bool IsDivideByZero(Exception ex)
        {
            if (ex is SqlException sqlEx && sqlEx.Number == 8134)
                return true;
    
            return ex.InnerException != null && IsDivideByZero(ex.InnerException);
        }
    }
    

    and register it with DI like this:

    private static void Configure(HttpConfiguration config)
    {
      // ...
      config.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());
      // ...
    }