OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
Other
453 stars 160 forks source link

Error reading multipart Batch Response #1276

Open GLuca74 opened 1 month ago

GLuca74 commented 1 month ago

Hello, I am trying to build an OData server using Microsoft.AspnetCore.OData. I have a problem when I try to send a Batch request with a multipart request. The problem seems happend in the response. In this repository, https://github.com/GLuca74/TestOData, there is a solution with 3 projects :

TestODataServer - The server TestODataClient - The client TestODataModels - A shared Entity

the client request is processed by the server and it is able to receive the response, but when I try to read the actual response I get an error. To read the response I use Microsoft.AspNet.WebApi.Client. This is the code :

                using (var response = await httpClient.SendAsync(request).ConfigureAwait(false))
                {
                    if (!response.IsSuccessStatusCode)
                    {
                        HttpRequestException ex = new HttpRequestException(await response.Content.ReadAsStringAsync(), null, response.StatusCode);
                        throw ex;
                    }
                    var multipart = await response.Content.ReadAsMultipartAsync(); 
                    foreach (var c in multipart.Contents)
                    {
                        var responses = await c.ReadAsMultipartAsync();
                        foreach (var r in responses.Contents)
                        {
                            var xx1 = await r.ReadAsHttpResponseMessageAsync(); //here i get an exception
                        }

                    }
                }

The exception is :

System.ArgumentException
  HResult=0x80070057
  Messaggio=Invalid 'HttpContent' instance provided. It does not have a content type header with a value of 'application/http; msgtype=response'. Arg_ParamName_Name
  Origine=System.Net.Http.Formatting
  Analisi dello stack:
   in System.Net.Http.HttpMessageContent.ValidateHttpMessageContent(HttpContent content, Boolean isRequest, Boolean throwOnError)
   in System.Net.Http.HttpContentMessageExtensions.ReadAsHttpResponseMessageAsync(HttpContent content, Int32 bufferSize, Int32 maxHeaderSize, CancellationToken cancellationToken)
   in System.Net.Http.HttpContentMessageExtensions.ReadAsHttpResponseMessageAsync(HttpContent content, Int32 bufferSize, Int32 maxHeaderSize)
   in System.Net.Http.HttpContentMessageExtensions.ReadAsHttpResponseMessageAsync(HttpContent content, Int32 bufferSize)
   in System.Net.Http.HttpContentMessageExtensions.ReadAsHttpResponseMessageAsync(HttpContent content)
   in TestODataClient.Program.<Main>d__0.MoveNext() in D:\VM\TestOData\TestODataClient\Program.cs: riga 75

sniffing request and response with Fiddler, i have this :

POST https://localhost:7200/$batch HTTP/1.1
Host: localhost:7200
OData-Version: 4.0
OData-MaxVersion: 4.0
Accept: multipart/mixed
Accept-Charset: UTF-8
Connection: Keep-Alive
Content-Type: multipart/mixed; boundary=batch_3a22c121-57c7-49d9-9cd5-3f6605bb80c6
Content-Length: 639

--batch_3a22c121-57c7-49d9-9cd5-3f6605bb80c6
Content-Type: multipart/mixed; boundary=changeset_1f9fece3-486f-4279-a568-8fffe53f3653

--changeset_1f9fece3-486f-4279-a568-8fffe53f3653
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

POST /Cities HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json; odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8

{"CityID":"bad222ce-f863-4647-ba4c-c0929eafb971","CityName":"Madrid"}
--changeset_1f9fece3-486f-4279-a568-8fffe53f3653--

--batch_3a22c121-57c7-49d9-9cd5-3f6605bb80c6--
HTTP/1.1 200 OK
Content-Type: multipart/mixed;boundary=batchresponse_d96b63e0-77dd-41aa-b3ac-0e9d6cb38562
Date: Thu, 11 Jul 2024 14:07:49 GMT
Server: Kestrel
Transfer-Encoding: chunked
OData-Version: 4.0

1fe
--batchresponse_d96b63e0-77dd-41aa-b3ac-0e9d6cb38562
Content-Type: multipart/mixed; boundary=changesetresponse_631283f4-b5fd-48b4-8e9e-4244ca32cb53

--changesetresponse_631283f4-b5fd-48b4-8e9e-4244ca32cb53
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 201 Created
Location: https://localhost:7200/Cities(bad222ce-f863-4647-ba4c-c0929eafb971)
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true; charset=utf-8
OData-Version: 4.0

fe
{"@odata.context":"https://localhost:7200/$metadata#Cities/$entity","CityID":"bad222ce-f863-4647-ba4c-c0929eafb971","CityName":"Madrid"}
--changesetresponse_631283f4-b5fd-48b4-8e9e-4244ca32cb53--
--batchresponse_d96b63e0-77dd-41aa-b3ac-0e9d6cb38562--

0

It seems that ReadAsHttpResponseMessageAsync not like this

--changesetresponse_631283f4-b5fd-48b4-8e9e-4244ca32cb53
Content-Type: application/http // msgtype=response seems missing

I tryed in a custom ODataBatchHandler to found a way to add msgtype=response to the response but i wanst able to found if is possible and where

I also tryed to use Microsoft.OData.Client and it works without problems, but I can not use this library.

Can you help me found a solution?

julealgon commented 1 month ago

Is there a particular reason why you are not using the native OData client to read the OData payloads? I'd recommend using that if you can, otherwise you are going to be reinventing the wheel a lot.

GLuca74 commented 1 month ago

Hello @julealgon , in the last years i wrote a no relational provider for entityframework core for a custom protocol. In the last months i rewrote the provider separating the part that comunicate with efcore structure and the part that implements my protocol. As result I have an assembly that is equivalent to Microsoft.EntityFrameworkCore.Relational(but for no relational) and an assembly that just implements my protocol. Now, using my no relational assembly I am writing an OData provider for entity framework core, so my code is running inside the SaveChange implementation of Entity framework. As general rule, I not like add reference to other assembly, for many reasons, if I fond a bug I have no control on the time to fix it, the support can ends etc. But in this specific case, OData client need its generated models to work while I have not a fixed model to work with because it depend on the model of the developer that uses my provider and honestly I can not image (and I not want to face) wath may means make ODataClient work with a "Dynamic" model.

Another reason is that OData Client returns already instanciated objects, I not need this, it is redundant because the object creation and properties assignments are made in the efcore shaper, I will be forced to generate all objects twice.

julealgon commented 1 month ago

Thanks for clarifying @GLuca74 , sounds like you have a fairly low-level, advanced case.

I know this doesn't necessarily answer your specific question, but have you tried using JSON OData batch request/responses instead of the raw multipart request approach? That format should be substantially simpler to parse as it returns a single JSON object like a normal API would.

GLuca74 commented 1 month ago

good morning @julealgon, yes, I saw that option and it is very confortable, but it was introduced in OData 4.01. This means that if i choice that way, I will be not able to use my provider with older version. Other OData servers may not implement this feature. I prefer try to implement the multipart rappresentation first, this gives me a wider range of use of my provider.

gathogojr commented 1 month ago

Hi @GLuca74. it was introduced in OData 4.01 - I don't believe this statement statement is valid. Json batch requests and responses are supported in OData V4

GLuca74 commented 1 month ago

Hi @gathogojr , I read this : https://docs.oasis-open.org/odata/new-in-odata/v4.01/cn04/new-in-odata-v4.01-cn04.html#_Toc21700090

"8.11 New: JSON Batch Requests and Responses Version 4.0 only defined a multipart format for describing batch requests and responses, which was somewhat hard to implement.

Version 4.01 introduces an alternative JSON format for batch requests and responses, so clients can now use off-the-shelf JSON libraries to compose batch requests and consume batch responses. Combined with the CSDL JSON representation [OData-CSDLJSON] this allows pure JSON communication with OData 4.01 services."