simple-odata-client / Simple.OData.Client

MIT License
329 stars 196 forks source link

Calling a function results in ODataException (When writing a JSON response...) #633

Open mladenb opened 5 years ago

mladenb commented 5 years ago

Hi,

I have an issue calling a simple function, using simple.odata.client library. In short, on the client side, I'm using an ASP.NET MVC web app, which calls our ASP.NET OData API, using the following code:

    var result = await Client
        .For<ResourceType>()
        .Function<EntityType>("my-function-multiple-name")
        .Set(new { q = "my query" })
        .ExecuteAsEnumerableAsync();

which results in the API returning the 200 OK result (inspected with Fiddler) with a proper collection in the response body, but the simple.odata.client raises an exception:

ODataException: When writing a JSON response, a user model must be specified and the entity set and entity type must be passed to the ODataMessageWriter.CreateODataResourceWriter method or the ODataResourceSerializationInfo must be set on the ODataResource or ODataResourceSet that is being written.

with a stack trace:

Microsoft.OData.ODataResourceTypeContext.ValidateAndReturn<T>(T value)
Microsoft.OData.Evaluation.ODataResourceMetadataContext+ODataResourceMetadataContextWithModel.ComputeETagPropertiesFromAnnotation()+MoveNext()
System.Linq.Enumerable.Any<TSource>(IEnumerable<TSource> source)
Microsoft.OData.Evaluation.ODataResourceMetadataContext+ODataResourceMetadataContextWithModel.get_ETagProperties()
Microsoft.OData.Evaluation.ODataConventionalEntityMetadataBuilder.GetETag()
Simple.OData.Client.V4.Adapter.ResponseReader.CreateAnnotations(ODataResource odataEntry)
Simple.OData.Client.V4.Adapter.ResponseReader.ConvertEntry(ResponseNode entryNode, object entry)
Simple.OData.Client.ResponseReaderBase.EndEntry(Stack<ResponseNode> nodeStack, ref ResponseNode rootNode, object entry)
Simple.OData.Client.V4.Adapter.ResponseReader.ReadResponse(ODataReader odataReader)
Simple.OData.Client.V4.Adapter.ResponseReader.GetResponseAsync(IODataResponseMessageAsync responseMessage)
Simple.OData.Client.ODataClient.ExecuteRequestWithResultAsync<T>(ODataRequest request, CancellationToken cancellationToken, Func<ODataResponse, T> createResult, Func<T> createEmptyResult, Func<T> createBatchResult)
Simple.OData.Client.ODataClient.ExecuteFunctionAsync(FluentCommand command, CancellationToken cancellationToken)
Simple.OData.Client.ODataClient.ExecuteAsEnumerableAsync(FluentCommand command, CancellationToken cancellationToken)
Simple.OData.Client.FluentClientBase<T, FT>.FilterAndTypeColumnsAsync(Task<IEnumerable<IDictionary<string, object>>> entries, IList<string> selectedColumns, string dynamicPropertiesContainerName)
MyApp.Services.OdataApi.MyService.GetMyMultipleFunction(string q) in MyService.cs

The server side code (OData API) for the function is this:

        [HttpGet]
        [ODataRoute("functions.my-function-multiple-name(q={query})")]
        [EnableQuery(PageSize = 10)]
        public IHttpActionResult GetMyMultipleFunction([FromODataUri] string query)
        {
            return Ok(GetRows(query));
        }

    private IQueryable<EntityType> GetRows(string query)
    {
        // returns a collection of EntityType
    }

Although when using the similar function, which returns a single entity as a result, instead of a collection, then everything works. So, on the client-side:

    var result = await Client
        .For<ResourceType>()
        .Function<EntityType>("my-function-single-name")
        .Set(new { q = "my query" })
        .ExecuteAsSingleAsync();

On the server side:

        [HttpGet]
        [ODataRoute("functions.my-function-single-name(q={query})")]
        [EnableQuery(PageSize = 10)]
        public IHttpActionResult GetMySingleFunction([FromODataUri] string query)
        {
            return Ok(new EntityType
            {
                A = 1,
                B = 2,
                C = 3,
                D = 4
            });
        }

The bottom line is, I can make the call to the function which returns a single entity, but it appears I can't call a function that returns a collection of entities.

The strange thing is that, looking into the Fiddler, I can see the server (api) responded with 200 OK, and returned the proper collection, but the simple.odata.client seems to have difficulties parsing it.

If useful, this is the response from the api server:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata.metadata=minimal
Expires: -1
OData-Version: 4.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 03 Jul 2019 14:59:03 GMT
Content-Length: 328

{"@odata.context":"http://example.com/MyApi/odata/v1/$metadata#Collection(myapi.Models.EntityType)","@odata.count":3,"value":[{"@odata.type":"#myapi.Models.EntityType","A":1,"B":2,"C":3,"D":4},{"@odata.type":"#myapi.Models.EntityType","A":1,"B":2,"C":3,"D":4},{"@odata.type":"#myapi.Models.EntityType","A":1,"B":2,"C":3,"D":4}]}

Any help would be highly appreciated. Thanks.

parthdshah9 commented 4 years ago

I'm trying to call unbound function call.

await _apiService.Container.Unbound().Function<EntityName>("GetListFunction").Set(new { WorkOrder = workOrder}).ExecuteAsEnumerableAsync();

API is working fine. /api/GetListFunction(WorkOrder = '12345678')

However, calling the function from client app throws an exception.

EXCEPTION

When writing a JSON response, a user model must be specified and the entity set and entity type must be passed to the ODataMessageWriter.CreateODataResourceWriter method or the ODataResourceSerializationInfo must be set on the ODataResource or ODataResourceSet that is being written.

STACK TRACE

at Microsoft.OData.ODataResourceTypeContext.ValidateAndReturn[T] (T value) [0x0001a] in :0 at Microsoft.OData.ODataResourceTypeContext.get_NavigationSourceName () [0x00000] in :0 at Microsoft.OData.Evaluation.ODataResourceMetadataContext+ODataResourceMetadataContextWithModel+d__17.MoveNext () [0x00032] in :0 at System.Linq.Enumerable.Any[TSource] (System.Collections.Generic.IEnumerable1[T] source) [0x00015] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.Linq/src/System/Linq/AnyAll.cs:20 at Microsoft.OData.Evaluation.ODataResourceMetadataContext+ODataResourceMetadataContextWithModel.get_ETagProperties () [0x0000f] in :0 at Microsoft.OData.Evaluation.ODataConventionalEntityMetadataBuilder.GetETag () [0x00034] in :0 at Microsoft.OData.ODataResourceBase.get_ETag () [0x00006] in :0 at Simple.OData.Client.V4.Adapter.ResponseReader.CreateAnnotations (Microsoft.OData.ODataResource odataEntry) [0x00068] in <4232e2ea6bf34164b6b1629240e9959b>:0 at Simple.OData.Client.V4.Adapter.ResponseReader.ConvertEntry (Simple.OData.Client.ResponseNode entryNode, System.Object entry) [0x0005b] in <4232e2ea6bf34164b6b1629240e9959b>:0 at Simple.OData.Client.ResponseReaderBase.EndEntry (System.Collections.Generic.Stack1[T] nodeStack, Simple.OData.Client.ResponseNode& rootNode, System.Object entry) [0x00007] in :0 at Simple.OData.Client.V4.Adapter.ResponseReader.ReadResponse (Microsoft.OData.ODataReader odataReader, Microsoft.OData.IODataResponseMessageAsync responseMessage) [0x0008c] in <4232e2ea6bf34164b6b1629240e9959b>:0 at Simple.OData.Client.V4.Adapter.ResponseReader.GetResponseAsync (Microsoft.OData.IODataResponseMessageAsync responseMessage) [0x002aa] in <4232e2ea6bf34164b6b1629240e9959b>:0 at Simple.OData.Client.ODataClient.ExecuteRequestWithResultAsync[T] (Simple.OData.Client.ODataRequest request, System.Threading.CancellationToken cancellationToken, System.Func2[T,TResult] createResult, System.Func1[TResult] createEmptyResult, System.Func1[TResult] createBatchResult) [0x001b7] in :0 at Simple.OData.Client.ODataClient.ExecuteFunctionAsync (Simple.OData.Client.ResolvedCommand command, System.Threading.CancellationToken cancellationToken) [0x00142] in :0 at Simple.OData.Client.ODataClient.ExecuteAsEnumerableAsync (Simple.OData.Client.FluentCommand command, System.Threading.CancellationToken cancellationToken) [0x00152] in :0 at Simple.OData.Client.FluentClientBase2[T,FT].FilterAndTypeColumnsAsync (System.Threading.Tasks.Task1[TResult] entries, System.Collections.Generic.IList1[T] selectedColumns, System.String dynamicPropertiesContainerName) [0x0009f] in :0

mehric commented 3 years ago

I am getting the same error using an unbound action and ExecuteAsSingleAsync. The action works but it generates the same exception. In my use case, I don't care about the action response at all, I ended up using an HTTP client and make the call myself.

RobTF commented 3 years ago

Also getting this.