AspNetCoreOData
AspNetCoreOData copied to clipboard
ODataException handling
Hello,
When you use [EnableQueryAttribute] and OData.Core throws ODataException, it's implicitly converted to a 400 Bad Request error with error details formatted as JSON.
When you use the ODataQueryOptions<T>.ApplyTo(IQueryable) approach, the user is responsible for handling ODataException, leading to inconsistent behavior. By default, you get a 500 error and an empty response body.
It should be easier to handle OData errors consistently using either approach. (Maybe move the error handling from EnableQueryAttribute to an exception filter attribute that can be applied to any controller action.)
Thx
Another option might be that the behavior for an odatacontroller automatically provides this error handling behavior. We would need a way for these folks to opt-out of this behavior where needed. We would also need a way for folks who are not using an odatacontroller to get this behavior applied to their controller methods, so probably we would need an attribute for that anyw
Given that we are likely to need attributes no matter the path forward, we should consider the overall design of what it would mean to have multiple odata-related attributes applied to a single controller method.
I don't think the solution for ODataQueryOptions<T>.ApplyTo(IQueryable) needs to be fully automatic. I use it precisely because it gives me more control than EnableQueryAttribute and overall feels less magic.
The problem is just that there is no good way to convert ODataException to a spec-compliant ActionResult. Providing an MVC FilterAttribute could be one way to fill that gap, another way could be to provide an API for doing the conversion.
[HttpGet, ODataErrorFilter]
public ActionResult<IQueryable<Product>> GetWithMvcFilterAttribute(ODataQueryOptions<Product> queryOptions)
{
IQueryable<Product> queryable = dbContext.Products.AsQueryable();
return options.ApplyTo(queryable);
}
[HttpGet]
public ActionResult<IQueryable<Product>> GetWithManualConversion(ODataQueryOptions<Product> queryOptions)
{
try
{
IQueryable<Product> queryable = dbContext.Products.AsQueryable();
return options.ApplyTo(queryable);
}
catch (ODataException ex)
{
ODataError odataError = ODataError.FromException(ex);
return BadRequest(odataError);
}
}