OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
855 stars 473 forks source link

Optional parameter not working for function with single argument #1678

Open flibustier7seas opened 6 years ago

flibustier7seas commented 6 years ago

When defining a function with single optional parameter, api returns 500 (Internal Server Error).

Assemblies affected

Microsoft.AspNet.OData 7.0.1

Reproduce steps

[ODataRoute("Users/GetFirstName()")]
public string GetFirstName()
{
    return GetFirstName("Unknown");
}

[ODataRoute("Users/GetFirstName(firstName={firstName})")]
public string GetFirstName(string firstName)
{
    return firstName;
}
var getFirstName = builder.EntityType<User>().Collection
    .Function("GetFirstName")
    .Returns<string>();

getFirstName.Parameter<string>("firstName").Optional().HasDefaultValue("Unknown");

Request

http://localhost:63000/v1/Users/GetFirstName()

Expected result

{
    "@odata.context": "http://localhost:63000/v1/$metadata#Edm.String",
    "value": "Unknown"
}

Actual result

Status: 500 Internal Server Error

Server Error in '/' Application.

The path template 'Users/GetFirstName()' on the action 'GetFirstName' in controller 'Users' is not a valid OData path template. Bad Request - Error in query syntax.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: The path template 'Users/GetFirstName()' on the action 'GetFirstName' in controller 'Users' is not a valid OData path template. Bad Request - Error in query syntax.

Additional detail

Example: https://github.com/flibustier7seas/odata-example/tree/optional-parameter#optional-parameter-not-working-for-function-with-single-argument

xuzhg commented 5 years ago

@flibustier7seas Optional parameter means the parameter maybe has default value. But for the function syntax, all parameters (include optional parameters) should be presented in the route template.

In your scenario, you should remove the first one and keep/change the second one as:

[ODataRoute("Users/GetFirstName(firstName={firstName})")]
public string GetFirstName([FromODataUri]string firstName = "Unknown")
{
    return firstName;
}

Hope it can help. Thanks.

flibustier7seas commented 5 years ago

@xuzhg

But for the function syntax, all parameters (include optional parameters) should be presented in the route template.

I can skip optional parameters in the route template for function with 2 parameters:

Request: http://localhost:63000/v1/Users/GetFullName(firstName='Foo')

var getFullName =  builder.EntityType<User>().Collection
    .Function("GetFullName")
    .Returns<string>();
getFullName.Parameter<string>("firstName").Required();
getFullName.Parameter<string>("lastName").Optional().HasDefaultValue("Unknown");
[ODataRoute("Users/GetFullName(firstName={firstName})")]
public string GetFullName(string firstName)
{
    return GetFullName(firstName, "Unknown");
}

Why cannot skip optional parameter in the route template for function with single optional parameter?

xuzhg commented 5 years ago

@flibustier7seas In my understanding, "optional" means its value can be optional, but not for the syntax. for a function or action, its syntax is function name plus the parameter type. That's different with the "method" in C#. I would like @mikepizzo can share his thoughts about the "optional"

flibustier7seas commented 5 years ago

@xuzhg In my understanding, when a parameter is marked as "optional", it can be omitted when the function is called, as in "C#"

https://www.odata.org/blog/OData-401-Committee-Spec-Published/

Optional Function Parameters – Function parameters can be annotated as optional. Optional parameters may be omitted when invoking the function.

flibustier7seas commented 5 years ago

@raheph @xuzhg @mikepizzo any updates on this issue?

denious commented 5 years ago

Any updates? Following.

xuzhg commented 5 years ago

@denious It should be fixed in the ODL. Would you please try the latest version and share us your finding. Thanks

denious commented 5 years ago

Running Microsoft.AspNetCore.OData 7.1.0 I confirm the same buggy behavior as originally described:

Expected 200 in these scenarios:

Only scenario that works is the last one, the first two return 404:

2019-03-28 05_12_20-Insomnia – Parts 2019-03-28 05_12_52-Insomnia – Parts 2019-03-28 05_12_39-Insomnia – Parts

Method signature:

[ODataRoute("GetSearchFilterOperators(filter={filter})")]
[HttpGet]
[Produces("application/json")]
[ProducesResponseType(typeof(ODataValue<IEnumerable<SearchFilterOperator>>), (int)HttpStatusCode.OK)]
public IQueryable<SearchFilterOperator> GetSearchFilterOperators([FromODataUri] string filter = null)

ODATA config for the function:

var getSearchFilerOperatorFunc = entityType.Collection
    .Function("GetSearchFilterOperators")
    .ReturnsCollection<SearchFilterOperator>();

var filterParam = getSearchFilerOperatorFunc.Parameter<string>("filter");
filterParam.Optional();
filterParam.HasDefaultValue(null);
prashantaggarwal1990 commented 3 years ago

@xuzhg Facing the same issue with Microsoft.AspNetCore.OData 7.5.6 and .Net core 3.1. Do we have any available resolution for this?

adrien-constant commented 3 years ago

Same for me, with .NET 5.0 Surprinsingly, it was working on ASP.NET MVC and .NET Framework

bmward80 commented 2 years ago

Running into this problem myself, I discovered you could overload the odata function configurations by adding the same-named function multiple times with different parameters:

builder.EntityType<ClientViewModel>()
    .Function("TaskActivityStatistics")
    .ReturnsFromEntitySet<TaskActivityStatistics>("TaskActivityStatistics");

var getClientTaskActivityStatistics = builder.EntityType<ClientViewModel>()
    .Function("TaskActivityStatistics")
    .ReturnsFromEntitySet<TaskActivityStatistics>("TaskActivityStatistics")
    .Parameter<string>("filter");

Which allowed me to create a single method on the controller that works for both. In my example: TaskActivityStatistics([FromODataUri] int key, string? filter = null)

Looking at the /$odata debug endpoint, both odata functions are registered properly and work as expected (still have to use the parenthesis, unfortunately):

{
    "DisplayName": "ClientsController.TaskActivityStatistics (WebApi)",
    "HttpMethods": [
        "GET"
    ],
    "Pattern": "odata/workspace/Clients({key})/TaskActivityStatistics()",
    "IsODataRoute": true
},
{
    "DisplayName": "ClientsController.TaskActivityStatistics (WebApi)",
    "HttpMethods": [
        "GET"
    ],
    "Pattern": "odata/workspace/Clients({key})/TaskActivityStatistics(filter={filter})",
    "IsODataRoute": true
},