0

If create weatherforecast webapi with swashbuckle support.

then create a console application using a connected service which successfully builds the client.

this works fine for example

    [HttpPost("Transform2")]
    public IAsyncEnumerable<WeatherForecast> Create()
    {
        async IAsyncEnumerable<WeatherForecast> answer()
        {
            yield return new WeatherForecast
            {
                Date = DateTime.Now,
                Summary = "",
                TemperatureC = 1
            };
        }
        return answer();
    }

and the client code looks like this

        using (var httpClient = new HttpClient(clientHandler))
        {
            var client = new swaggerClient("https://localhost:44330/", httpClient);
            var works = await client.Transform2Async();
        }

nice, but actually what I'm working towards is sending a stream of data to the server and having it process it and return some data back, so I've decided to do that using IFormFile.

i.e.

    [HttpPost("Transform")]
    public IAsyncEnumerable<WeatherForecast> Create(IFormFile file)
    {
        async IAsyncEnumerable<WeatherForecast> answer()
        {
            yield return new WeatherForecast
            {
                Date = DateTime.Now,
                Summary = "",
                TemperatureC = 1
            };
        }
        return answer();
    }

this seems to work when I invoke it from the swagger html documentation page, it prompts me for a file to upload and this invoked the required API call.

The problem is when I use it from a openapi connected service generated code. This maps the IFormFile to a stream client side (which doesnt seem unreasonable).

so like this, just send any old file.

        using (var httpClient = new HttpClient(clientHandler))
        {
            var client = new swaggerClient("https://localhost:44330/", httpClient);
            //var works = await client.Transform2Async();
            using (var file = System.IO.File.OpenRead(@"....\Program.cs"))
            {
                var fails = await client.TransformAsync(file);
            }
        }

this then fails with

ReportWebAPIProxy.ApiException
  HResult=0x80131500
  Message=The HTTP status code of the response was not expected (400).

Status: 400
Response: 
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-0db7c532df85014e9f53bd7afbfae292-0f8b0596dc7d6f43-00","errors":{"":["Failed to read the request form. Missing content-type boundary."]}}
  Source=ReportWebAPIProxy
  StackTrace:
   at ReportWebAPIProxy.swaggerClient.<TransformAsync>d__16.MoveNext() in C:\Users\m_r_n\source\repos\ReportWebAPIProxy\ReportWebAPIProxy\obj\swaggerClient.cs:line 170
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at ReportWebAPIProxy.Program.<DisplayStudents>d__1.MoveNext() in C:\Users\m_r_n\source\repos\ReportWebAPIProxy\ReportWebAPIProxy\Program.cs:line 26
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at ReportWebAPIProxy.Program.<Main>d__0.MoveNext() in C:\Users\m_r_n\source\repos\ReportWebAPIProxy\ReportWebAPIProxy\Program.cs:line 11

  This exception was originally thrown at this call stack:
    ReportWebAPIProxy.swaggerClient.TransformAsync(System.IO.Stream, System.Threading.CancellationToken) in swaggerClient.cs
    [External Code]
    ReportWebAPIProxy.Program.DisplayStudents() in Program.cs
    [External Code]
    ReportWebAPIProxy.Program.Main(string[]) in Program.cs
MrD at KookerellaLtd
  • 2,412
  • 1
  • 15
  • 17

1 Answers1

0

So it appears to be a bug in NSwag, which generates the swagger client code.

For the help of other people, this is how I worked out how to get around it.

These are clues.

https://github.com/RicoSuter/NSwag/issues/2493 https://github.com/RicoSuter/NSwag/issues/2755

If you take a look at your generated swagger documentation on the server and execute the REST post, it should tell you the equivalent curl command.

In my case thats

curl -X POST "https://localhost:44330/WeatherForecast/Transform" -H  "accept: text/plain" -H  "Content-Type: multipart/form-data" -F "file=@reportimport.log"

then go and look at the code generated by nswag (go to the connected service and click the "..." and view generated code).

my code said this..

            using (var request_ = new System.Net.Http.HttpRequestMessage())
            {
                var content_ = new System.Net.Http.StreamContent(body);
                content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("multipart/form-data");
                request_.Content = content_;
                request_.Method = new System.Net.Http.HttpMethod("POST");
                request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));

so seemingly no mention of the

-F "file=@reportimport.log"

that I can see in my curl.

so then look up how to actually do a multipart form data post, so

How to upload file to server with HTTP POST multipart/form-data?

and with a bit of guesswork you'd get

            using (var request_ = new System.Net.Http.HttpRequestMessage())
            {
                var content_ = new System.Net.Http.MultipartFormDataContent();
                content_.Add(new System.Net.Http.StringContent("madeup.txt"), "file");
                content_.Add(new System.Net.Http.StreamContent(body));
                //var content_ = new System.Net.Http.StreamContent(body);
                //content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("multipart/form-data");
                request_.Content = content_;

etc

and I think this DOES work...or at least doesnt error, I'll check.

so that doesnt fix the underlying issue of the bug in the codegen, but at least you have a recipe to fix the auto generated code.

MrD at KookerellaLtd
  • 2,412
  • 1
  • 15
  • 17