M Leipper
M Leipper

Reputation: 397

override odata error response so stack trace is hidden

I'm trying to set up a project with Odata, correctly when a user sends up an incorrectly formatted request I receive the full stack traceback in the request. I would like to override this error message such that I can make use of a custom message and omit the stack trace from the data that is returned to the user.

 {
    "error": {
        "code": "",
        "message": "The query specified in the URI is not valid. Could not find a property named 'namee' on type 'Edocs.API.Springboard.Core.Site'.",
        "details": [],
        "innererror": {
            "message": "Could not find a property named 'namee' on type 'Edocs.API.Springboard.Core.Site'.",
            "type": "Microsoft.OData.ODataException",
            "stacktrace": "   at Microsoft.OData.UriParser.EndPathBinder.GeneratePropertyAccessQueryForOpenType(EndPathToken endPathToken, SingleValueNode parentNode)\r\n   at Microsoft.OData.UriParser.EndPathBinder.BindEndPath(EndPathToken endPathToken)\r\n   at Microsoft.OData.UriParser.MetadataBinder.BindEndPath(EndPathToken endPathToken)\r\n   at Microsoft.OData.UriParser.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.UriParser.OrderByBinder.ProcessSingleOrderBy(BindingState state, OrderByClause thenBy, OrderByToken orderByToken)\r\n   at Microsoft.OData.UriParser.OrderByBinder.BindOrderBy(BindingState state, IEnumerable`1 orderByTokens)\r\n   at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseOrderByImplementation(String orderBy, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)\r\n   at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseOrderBy()\r\n   at Microsoft.AspNet.OData.Query.OrderByQueryOption.get_OrderByClause()\r\n   at Microsoft.AspNet.OData.Query.Validators.OrderByQueryValidator.Validate(OrderByQueryOption orderByOption, ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.Query.OrderByQueryOption.Validate(ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.CreateAndValidateQueryOptions(HttpRequest request, ODataQueryContext queryContext)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.<>c__DisplayClass1_0.<OnActionExecuted>b__1(ODataQueryContext queryContext)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, Func`2 modelFunction, IWebApiRequestMessage request, Func`2 createQueryOptionFunction)\r\n   at Microsoft.AspNet.OData.EnableQueryAttribute.OnActionExecuted(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, IWebApiRequestMessage request, Func`2 modelFunction, Func`2 createQueryOptionFunction, Action`1 createResponseAction, Action`3 createErrorAction)"
        }
    }
}

I'm assuming the wrapping for this error occurs in [EnableQuery], however, I'm not sure what method in this I have to override to get the result that I want.

Thank you for any help in advance.

Below I've included what I've tried so far I'm using .net 5, Microsoft.AspNetCore.OData 7.5.2 And Entity framework for data retrival.

So Far I've tried making use of CustomEnableQueryAttribute As described in this question

    public class CustomEnableQueryAttribute : EnableQueryAttribute
{
    public override void ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)
    {
        try
        {
            base.ValidateQuery(request, queryOptions);
        }
        catch (ODataException e)
        {
           throw new  CustomException(e.Message, e) { UserMessage = "Invalid OData query." };
        }
    }

}


public class CustomException: ODataException
{
    public string UserMessage;
    public CustomException(string message, ODataException oData) : base(message)
    {
        
    }
}

this failed to resolve the issue.

I've also tried :

public class CustomODataOutputFormatter : ODataOutputFormatter
{
    private readonly JsonSerializer serializer;
    private readonly bool isDevelopment;

    public CustomODataOutputFormatter(bool isDevelopment)
      : base(new[] { ODataPayloadKind.Error })
    {
        this.serializer = new JsonSerializer { ContractResolver = new CamelCasePropertyNamesContractResolver() };
        this.isDevelopment = isDevelopment;

        this.SupportedMediaTypes.Add("application/json");
        this.SupportedEncodings.Add(new UTF8Encoding());
    }

    public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        if (!(context.Object is SerializableError serializableError))
        {
            return base.WriteResponseBodyAsync(context, selectedEncoding);
        }

        var error = serializableError.CreateODataError(this.isDevelopment);
        using (var writer = new StreamWriter(context.HttpContext.Response.Body))
        {
            this.serializer.Serialize(writer, error);
            return writer.FlushAsync();
        }
    }
}

public static class CommonExtensions
{
    public const string DefaultODataErrorMessage = "A server error occurred.";

    public static ODataError CreateODataError(this SerializableError serializableError, bool isDevelopment)
    {
        // ReSharper disable once InvokeAsExtensionMethod
        var convertedError = SerializableErrorExtensions.CreateODataError(serializableError);
        var error = new ODataError();
        if (isDevelopment)
        {
            error = convertedError;
        }
        else
        {
            // Sanitise the exposed data when in release mode.
            // We do not want to give the public access to stack traces, etc!
            error.Message = DefaultODataErrorMessage;
            error.Details = new[] { new ODataErrorDetail { Message = convertedError.Message } };
        }

        return error;
    }

    public static ODataError CreateODataError(this Exception ex, bool isDevelopment)
    {
        var error = new ODataError();

        if (isDevelopment)
        {
            error.Message = ex.Message;
            error.InnerError = new ODataInnerError(ex);
        }
        else
        {
            error.Message = DefaultODataErrorMessage;
            error.Details = new[] { new ODataErrorDetail { Message = ex.Message } };
        }

        return error;
    }
}

and with this method in the startup class:

            app.UseExceptionHandler(appBuilder =>
        {
            appBuilder.Use(async (context, next) =>
            {
                var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
                if (error?.Error != null)
                {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.ContentType = "application/json";

                    var response = error.Error.CreateODataError(false);
                    await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
                }

                // when no error, do next.
                else await next();
            });
        });

none of these resulted in the error being overridden,

Upvotes: 1

Views: 1131

Answers (1)

pil0t
pil0t

Reputation: 2185

Problem is in

public class CustomException: ODataException

Because ODataException is inherited from InvalidOperationException, and handled in special way inside EnableQueryAttribute

If you change it to

public class CustomException: Exception

You will be able to handle it later with app.UseExceptionHandler(appBuilder =>....

Upvotes: 3

Related Questions