AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

Custom FilterBinder doesn't work on actions with querying via ODataQueryOptionParser

Open a-ney opened this issue 1 year ago • 7 comments

I'm implementing odata api using ASP.NET Core OData 8.2.3 and I'm trying to make custom functions for $filter clause as described here https://devblogs.microsoft.com/odata/customizing-filter-for-spatial-data-in-asp-net-core-odata-8/

I created CustomFilterBinder and injected it exactly as described in the blog post but when I try to use my function like this $filter=custom('Foo') eq 1&$top=100 I get

{
    "error": {
        "code": "",
        "message": "The query specified in the URI is not valid. An unknown function with name 'custom' was found. This may also be a function import or a key lookup on a navigation property, which is not allowed.",
        "details": [],
        "innererror": {
            "message": "An unknown function with name 'custom' was found. This may also be a function import or a key lookup on a navigation property, which is not allowed.",
            "type": "Microsoft.OData.ODataException",
            "stacktrace": "   at Microsoft.OData.UriParser.FunctionCallBinder.GetUriFunctionSignatures(String functionCallToken, Boolean enableCaseInsensitive)
at Microsoft.OData.UriParser.FunctionCallBinder.BindAsUriFunction(FunctionCallToken functionCallToken, List`1 argumentNodes)
 at Microsoft.OData.UriParser.FunctionCallBinder.BindFunctionCall(FunctionCallToken functionCallToken)
at Microsoft.OData.UriParser.MetadataBinder.BindFunctionCall(FunctionCallToken functionCallToken)
 at Microsoft.OData.UriParser.MetadataBinder.Bind(QueryToken token)
 at Microsoft.OData.UriParser.BinaryOperatorBinder.GetOperandFromToken(BinaryOperatorKind operatorKind, QueryToken queryToken)
 at Microsoft.OData.UriParser.BinaryOperatorBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)
 at Microsoft.OData.UriParser.MetadataBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)
 at Microsoft.OData.UriParser.MetadataBinder.Bind(QueryToken token)
 at Microsoft.OData.UriParser.FilterBinder.BindFilter(QueryToken filter)
 at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseFilterImplementation(String filter, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)
 at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseFilter()
 at Microsoft.AspNetCore.OData.Query.FilterQueryOption.get_FilterClause()
 at Microsoft.AspNetCore.OData.Query.Validator.FilterQueryValidator.Validate(FilterQueryOption filterQueryOption, ODataValidationSettings settings)
 at Microsoft.AspNetCore.OData.Query.FilterQueryOption.Validate(ODataValidationSettings validationSettings)
 at Microsoft.AspNetCore.OData.Query.Validator.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)
 at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)
 at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)
 at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuting(ActionExecutingContext actionExecutingContext)"
        }
    }
}

Stacktrace say's it's because of EnableQuery attribute on my GET action which uses it's own FilterBinder instead of CustomFilterBinder. How do I make them work together?

a-ney avatar Mar 20 '24 10:03 a-ney

Stacktrace say's it's because of EnableQuery attribute on my GET action which uses it's own FilterBinder instead of CustomFilterBinder.

Does it work fine if you remove the [EnableQuery] attribute and manually handle a ODataQueryOptions<T> instead?

julealgon avatar Mar 20 '24 14:03 julealgon

@julealgon No, exception is the same.

Microsoft.OData.ODataException: An unknown function with name 'custom' was found. This may also be a function import or a key lookup on a navigation property, which is not allowed.
 at Microsoft.OData.UriParser.FunctionCallBinder.GetUriFunctionSignatures(String functionCallToken, Boolean enableCaseInsensitive)
 at Microsoft.OData.UriParser.FunctionCallBinder.BindAsUriFunction(FunctionCallToken functionCallToken, List`1 argumentNodes)
 at Microsoft.OData.UriParser.FunctionCallBinder.BindFunctionCall(FunctionCallToken functionCallToken)
 at Microsoft.OData.UriParser.MetadataBinder.BindFunctionCall(FunctionCallToken functionCallToken)
 at Microsoft.OData.UriParser.MetadataBinder.Bind(QueryToken token)
 at Microsoft.OData.UriParser.BinaryOperatorBinder.GetOperandFromToken(BinaryOperatorKind operatorKind, QueryToken queryToken)
 at Microsoft.OData.UriParser.BinaryOperatorBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)
 at Microsoft.OData.UriParser.MetadataBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)
 at Microsoft.OData.UriParser.MetadataBinder.Bind(QueryToken token)
 at Microsoft.OData.UriParser.FilterBinder.BindFilter(QueryToken filter)
 at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseFilterImplementation(String filter, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)
 at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseFilter()
 at Microsoft.AspNetCore.OData.Query.FilterQueryOption.get_FilterClause()
 at Microsoft.AspNetCore.OData.Query.FilterQueryOption.ApplyTo(IQueryable query, ODataQuerySettings querySettings)
 at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query, ODataQuerySettings querySettings)
 at Microsoft.AspNetCore.OData.Query.ODataQueryOptions`1.ApplyTo(IQueryable query, ODataQuerySettings querySettings)
 at Microsoft.AspNetCore.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query)
 at Microsoft.AspNetCore.OData.Query.ODataQueryOptions`1.ApplyTo(IQueryable query)\r\n
 at ... //my action call below

a-ney avatar Mar 20 '24 14:03 a-ney

Thanks @a-ney. I just wanted to make sure this was independent of [EnableQuery] itself. Hopefully the OData team will have more information to share with you about this.

In the meantime, you might want to rename your issue to remove the coupling with [EnableQuery] since it happens regardless of it.

julealgon avatar Mar 20 '24 16:03 julealgon

@a-ney the methods (for example, geo.distance) listed in the https://devblogs.microsoft.com/odata/customizing-filter-for-spatial-data-in-asp-net-core-odata-8/ are built-in methods, it means that's supported by default in the OData library, but there's no default binding in the ASP.NET Core side . So, The post is trying to bind the methods to a particular C# method.

Your scenario is different, You want to 'define' a cusomized method that is not defined in the OData. You should use the "customized Uri function" feature. It's listed at https://learn.microsoft.com/en-us/odata/webapi/bind-custom-urifunctions-to-clr-methods.

Here's a version from my side:

public static void AddCustomFunc()
{
    FunctionSignatureWithReturnType customStringEdmFunction =
             new FunctionSignatureWithReturnType(
            EdmCoreModel.Instance.GetInt32(true),
            EdmCoreModel.Instance.GetString(true));

    MethodInfo customStringMethodInfo = typeof(Extensions).GetMethod("MyCustomMethod", new Type[] { typeof(string) });
    const string customMethodName = "custom";
    ODataUriFunctions.AddCustomUriFunction(customMethodName, customStringEdmFunction, customStringMethodInfo);
}

public static int MyCustomMethod(string input)
{
    // you should replace the codes using your logic
    switch (input)
    {
        case "foo": return 1;
        case "bar": return 2;
        case "tik": return 3;
        case "tok": return 4;
        default: return 0;
    }
}

Then, call 'AddCustomFunc' some place in 'Program.cs' before call using middleware. Then it should work (you don't need to customize the IFilterBinder);

(Known issue: AddCustomUriFunction uses the 'static' to hold the function mapping, it's not good design and better to refactor it in the ODL 8).

xuzhg avatar Mar 25 '24 19:03 xuzhg

@xuzhg wow, thanks a lot, this is exactly what I needed!

a-ney avatar Mar 26 '24 14:03 a-ney

reopen for more discussion about the refactor

xuzhg avatar Mar 26 '24 16:03 xuzhg

@a-ney Can you provide working sample of this?

rbalagangadharan avatar Apr 20 '24 13:04 rbalagangadharan