From 88fa5d70d7764ca5e09281b08b4f80d8153ef212 Mon Sep 17 00:00:00 2001 From: Blackclaws Date: Fri, 19 Mar 2021 18:01:21 +0100 Subject: [PATCH] [csharp][csharp-netcore] Fix Fileupload for Request Bodies for RestSharp and HttpClient libraries (#9010) * Fix Request Body File Upload for RestSharp and csharp-netcore * Update Samples * Fix missing using and update samples * Enable upload test * Fix form data and file upload handling for httpclient, update samples --- .../csharp-netcore/ApiClient.mustache | 39 +++++--- .../libraries/httpclient/ApiClient.mustache | 88 ++++++++++++------- .../Org.OpenAPITools.Test/Api/PetApiTests.cs | 2 +- .../src/Org.OpenAPITools/Client/ApiClient.cs | 86 +++++++++++------- .../src/Org.OpenAPITools/Client/ApiClient.cs | 39 +++++--- .../src/Org.OpenAPITools/Client/ApiClient.cs | 39 +++++--- .../src/Org.OpenAPITools/Client/ApiClient.cs | 39 +++++--- .../src/Org.OpenAPITools/Client/ApiClient.cs | 39 +++++--- 8 files changed, 242 insertions(+), 129 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache b/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache index 3414ccaf8b33..6d0e25d93f49 100644 --- a/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache +++ b/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache @@ -331,25 +331,40 @@ namespace {{packageName}}.Client if (options.Data != null) { - if (options.HeaderParameters != null) + if (options.Data is Stream stream) { - var contentTypes = options.HeaderParameters["Content-Type"]; - if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + var contentType = "application/octet-stream"; + if (options.HeaderParameters != null) { - request.RequestFormat = DataFormat.Json; - } - else - { - // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + var contentTypes = options.HeaderParameters["Content-Type"]; + contentType = contentTypes[0]; } + + var bytes = ClientUtils.ReadAsBytes(stream); + request.AddParameter(contentType, bytes, ParameterType.RequestBody); } else { - // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. - request.RequestFormat = DataFormat.Json; - } + if (options.HeaderParameters != null) + { + var contentTypes = options.HeaderParameters["Content-Type"]; + if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + { + request.RequestFormat = DataFormat.Json; + } + else + { + // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + } + } + else + { + // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. + request.RequestFormat = DataFormat.Json; + } - request.AddJsonBody(options.Data); + request.AddJsonBody(options.Data); + } } if (options.FileParameters != null) diff --git a/modules/openapi-generator/src/main/resources/csharp-netcore/libraries/httpclient/ApiClient.mustache b/modules/openapi-generator/src/main/resources/csharp-netcore/libraries/httpclient/ApiClient.mustache index 9c6b01d52efd..ad2ca7ce4b51 100644 --- a/modules/openapi-generator/src/main/resources/csharp-netcore/libraries/httpclient/ApiClient.mustache +++ b/modules/openapi-generator/src/main/resources/csharp-netcore/libraries/httpclient/ApiClient.mustache @@ -24,6 +24,7 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; using System.Net.Http; {{/useWebRequest}} using System.Net.Http; +using System.Net.Http.Headers; {{#supportsRetry}} using Polly; {{/supportsRetry}} @@ -214,6 +215,32 @@ namespace {{packageName}}.Client {{/reUseHttpClient}} } + /// Prepares multipart/form-data content + {{! TODO: Add handling of improper usage }} + HttpContent PrepareMultipartFormDataContent(RequestOptions options) + { + string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant(); + var multipartContent = new MultipartFormDataContent(boundary); + foreach (var formParameter in options.FormParameters) + { + multipartContent.Add(new StringContent(formParameter.Value), formParameter.Key); + } + + if (options.FileParameters != null && options.FileParameters.Count > 0) + { + foreach (var fileParam in options.FileParameters) + { + var fileStream = fileParam.Value as FileStream; + var fileStreamName = fileStream != null ? System.IO.Path.GetFileName(fileStream.Name) : null; + var content = new StreamContent(fileParam.Value); + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + multipartContent.Add(content, fileParam.Key, + fileStreamName ?? "no_file_name_provided"); + } + } + return multipartContent; + } + /// /// Provides all logic for constructing a new HttpRequestMessage. /// At this point, all information for querying the service is known. Here, it is simply @@ -270,52 +297,45 @@ namespace {{packageName}}.Client List> contentList = new List>(); - if (options.FormParameters != null && options.FormParameters.Count > 0) + string contentType = null; + if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type")) { - contentList.Add(new Tuple(new FormUrlEncodedContent(options.FormParameters), null, null)); + var contentTypes = options.HeaderParameters["Content-Type"]; + contentType = contentTypes.FirstOrDefault(); } - if (options.Data != null) + {{!// TODO Add error handling in case of improper usage}} + if (contentType == "multipart/form-data") { - var serializer = new CustomJsonCodec(SerializerSettings, configuration); - contentList.Add( - new Tuple(new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), "application/json"), null, null)); + request.Content = PrepareMultipartFormDataContent(options); } - - if (options.FileParameters != null && options.FileParameters.Count > 0) + else if (contentType == "application/x-www-form-urlencoded") { - foreach (var fileParam in options.FileParameters) - { - var bytes = ClientUtils.ReadAsBytes(fileParam.Value); - var fileStream = fileParam.Value as FileStream; - contentList.Add(new Tuple(new ByteArrayContent(bytes), fileParam.Key, - fileStream?.Name ?? "no_file_name_provided")); - } + request.Content = new FormUrlEncodedContent(options.FormParameters); } - - if (contentList.Count > 1) + else { - string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant(); - var multipartContent = new MultipartFormDataContent(boundary); - foreach (var content in contentList) + if (options.Data != null) { - if(content.Item2 != null) - { - multipartContent.Add(content.Item1, content.Item2, content.Item3); - } - else - { - multipartContent.Add(content.Item1); - } - } + if (options.Data is Stream s) + { + contentType ??= "application/octet-stream"; - request.Content = multipartContent; - } - else - { - request.Content = contentList.FirstOrDefault()?.Item1; + var streamContent = new StreamContent(s); + streamContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); + request.Content = streamContent; + } + else + { + var serializer = new CustomJsonCodec(SerializerSettings, configuration); + request.Content = new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), + "application/json"); + } + } } + + // TODO provide an alternative that allows cookies per request instead of per API client if (options.Cookies != null && options.Cookies.Count > 0) { diff --git a/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools.Test/Api/PetApiTests.cs b/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools.Test/Api/PetApiTests.cs index f64ed20e7cf0..eca3164a17fd 100644 --- a/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools.Test/Api/PetApiTests.cs +++ b/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools.Test/Api/PetApiTests.cs @@ -284,7 +284,7 @@ public void UpdatePetWithFormTest() /// /// Test UploadFile /// - [Fact (Skip = "file upload not working for httpclient yet")] + [Fact] public void UploadFileTest() { Assembly _assembly = Assembly.GetExecutingAssembly(); diff --git a/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools/Client/ApiClient.cs b/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools/Client/ApiClient.cs index 51d8102075bf..60ba40907766 100644 --- a/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools/Client/ApiClient.cs +++ b/samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools/Client/ApiClient.cs @@ -26,6 +26,7 @@ using Newtonsoft.Json.Serialization; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; using System.Net.Http; +using System.Net.Http.Headers; using Polly; namespace Org.OpenAPITools.Client @@ -202,6 +203,31 @@ public ApiClient(String basePath) _baseUrl = basePath; } + /// Prepares multipart/form-data content + HttpContent PrepareMultipartFormDataContent(RequestOptions options) + { + string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant(); + var multipartContent = new MultipartFormDataContent(boundary); + foreach (var formParameter in options.FormParameters) + { + multipartContent.Add(new StringContent(formParameter.Value), formParameter.Key); + } + + if (options.FileParameters != null && options.FileParameters.Count > 0) + { + foreach (var fileParam in options.FileParameters) + { + var fileStream = fileParam.Value as FileStream; + var fileStreamName = fileStream != null ? System.IO.Path.GetFileName(fileStream.Name) : null; + var content = new StreamContent(fileParam.Value); + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + multipartContent.Add(content, fileParam.Key, + fileStreamName ?? "no_file_name_provided"); + } + } + return multipartContent; + } + /// /// Provides all logic for constructing a new HttpRequestMessage. /// At this point, all information for querying the service is known. Here, it is simply @@ -258,52 +284,44 @@ private HttpRequestMessage NewRequest( List> contentList = new List>(); - if (options.FormParameters != null && options.FormParameters.Count > 0) + string contentType = null; + if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type")) { - contentList.Add(new Tuple(new FormUrlEncodedContent(options.FormParameters), null, null)); + var contentTypes = options.HeaderParameters["Content-Type"]; + contentType = contentTypes.FirstOrDefault(); } - if (options.Data != null) + if (contentType == "multipart/form-data") { - var serializer = new CustomJsonCodec(SerializerSettings, configuration); - contentList.Add( - new Tuple(new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), "application/json"), null, null)); + request.Content = PrepareMultipartFormDataContent(options); } - - if (options.FileParameters != null && options.FileParameters.Count > 0) + else if (contentType == "application/x-www-form-urlencoded") { - foreach (var fileParam in options.FileParameters) - { - var bytes = ClientUtils.ReadAsBytes(fileParam.Value); - var fileStream = fileParam.Value as FileStream; - contentList.Add(new Tuple(new ByteArrayContent(bytes), fileParam.Key, - fileStream?.Name ?? "no_file_name_provided")); - } + request.Content = new FormUrlEncodedContent(options.FormParameters); } - - if (contentList.Count > 1) + else { - string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant(); - var multipartContent = new MultipartFormDataContent(boundary); - foreach (var content in contentList) + if (options.Data != null) { - if(content.Item2 != null) - { - multipartContent.Add(content.Item1, content.Item2, content.Item3); - } - else - { - multipartContent.Add(content.Item1); - } - } + if (options.Data is Stream s) + { + contentType ??= "application/octet-stream"; - request.Content = multipartContent; - } - else - { - request.Content = contentList.FirstOrDefault()?.Item1; + var streamContent = new StreamContent(s); + streamContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); + request.Content = streamContent; + } + else + { + var serializer = new CustomJsonCodec(SerializerSettings, configuration); + request.Content = new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), + "application/json"); + } + } } + + // TODO provide an alternative that allows cookies per request instead of per API client if (options.Cookies != null && options.Cookies.Count > 0) { diff --git a/samples/client/petstore/csharp-netcore/OpenAPIClient-net47/src/Org.OpenAPITools/Client/ApiClient.cs b/samples/client/petstore/csharp-netcore/OpenAPIClient-net47/src/Org.OpenAPITools/Client/ApiClient.cs index 55b5d137d6fb..db5a263b6394 100644 --- a/samples/client/petstore/csharp-netcore/OpenAPIClient-net47/src/Org.OpenAPITools/Client/ApiClient.cs +++ b/samples/client/petstore/csharp-netcore/OpenAPIClient-net47/src/Org.OpenAPITools/Client/ApiClient.cs @@ -331,25 +331,40 @@ private RestRequest NewRequest( if (options.Data != null) { - if (options.HeaderParameters != null) + if (options.Data is Stream stream) { - var contentTypes = options.HeaderParameters["Content-Type"]; - if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + var contentType = "application/octet-stream"; + if (options.HeaderParameters != null) { - request.RequestFormat = DataFormat.Json; - } - else - { - // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + var contentTypes = options.HeaderParameters["Content-Type"]; + contentType = contentTypes[0]; } + + var bytes = ClientUtils.ReadAsBytes(stream); + request.AddParameter(contentType, bytes, ParameterType.RequestBody); } else { - // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. - request.RequestFormat = DataFormat.Json; - } + if (options.HeaderParameters != null) + { + var contentTypes = options.HeaderParameters["Content-Type"]; + if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + { + request.RequestFormat = DataFormat.Json; + } + else + { + // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + } + } + else + { + // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. + request.RequestFormat = DataFormat.Json; + } - request.AddJsonBody(options.Data); + request.AddJsonBody(options.Data); + } } if (options.FileParameters != null) diff --git a/samples/client/petstore/csharp-netcore/OpenAPIClient-net5.0/src/Org.OpenAPITools/Client/ApiClient.cs b/samples/client/petstore/csharp-netcore/OpenAPIClient-net5.0/src/Org.OpenAPITools/Client/ApiClient.cs index 55b5d137d6fb..db5a263b6394 100644 --- a/samples/client/petstore/csharp-netcore/OpenAPIClient-net5.0/src/Org.OpenAPITools/Client/ApiClient.cs +++ b/samples/client/petstore/csharp-netcore/OpenAPIClient-net5.0/src/Org.OpenAPITools/Client/ApiClient.cs @@ -331,25 +331,40 @@ private RestRequest NewRequest( if (options.Data != null) { - if (options.HeaderParameters != null) + if (options.Data is Stream stream) { - var contentTypes = options.HeaderParameters["Content-Type"]; - if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + var contentType = "application/octet-stream"; + if (options.HeaderParameters != null) { - request.RequestFormat = DataFormat.Json; - } - else - { - // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + var contentTypes = options.HeaderParameters["Content-Type"]; + contentType = contentTypes[0]; } + + var bytes = ClientUtils.ReadAsBytes(stream); + request.AddParameter(contentType, bytes, ParameterType.RequestBody); } else { - // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. - request.RequestFormat = DataFormat.Json; - } + if (options.HeaderParameters != null) + { + var contentTypes = options.HeaderParameters["Content-Type"]; + if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + { + request.RequestFormat = DataFormat.Json; + } + else + { + // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + } + } + else + { + // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. + request.RequestFormat = DataFormat.Json; + } - request.AddJsonBody(options.Data); + request.AddJsonBody(options.Data); + } } if (options.FileParameters != null) diff --git a/samples/client/petstore/csharp-netcore/OpenAPIClient/src/Org.OpenAPITools/Client/ApiClient.cs b/samples/client/petstore/csharp-netcore/OpenAPIClient/src/Org.OpenAPITools/Client/ApiClient.cs index 20ce7c8767c6..c3d43b29bb46 100644 --- a/samples/client/petstore/csharp-netcore/OpenAPIClient/src/Org.OpenAPITools/Client/ApiClient.cs +++ b/samples/client/petstore/csharp-netcore/OpenAPIClient/src/Org.OpenAPITools/Client/ApiClient.cs @@ -330,25 +330,40 @@ private RestRequest NewRequest( if (options.Data != null) { - if (options.HeaderParameters != null) + if (options.Data is Stream stream) { - var contentTypes = options.HeaderParameters["Content-Type"]; - if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + var contentType = "application/octet-stream"; + if (options.HeaderParameters != null) { - request.RequestFormat = DataFormat.Json; - } - else - { - // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + var contentTypes = options.HeaderParameters["Content-Type"]; + contentType = contentTypes[0]; } + + var bytes = ClientUtils.ReadAsBytes(stream); + request.AddParameter(contentType, bytes, ParameterType.RequestBody); } else { - // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. - request.RequestFormat = DataFormat.Json; - } + if (options.HeaderParameters != null) + { + var contentTypes = options.HeaderParameters["Content-Type"]; + if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + { + request.RequestFormat = DataFormat.Json; + } + else + { + // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + } + } + else + { + // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. + request.RequestFormat = DataFormat.Json; + } - request.AddJsonBody(options.Data); + request.AddJsonBody(options.Data); + } } if (options.FileParameters != null) diff --git a/samples/client/petstore/csharp-netcore/OpenAPIClientCore/src/Org.OpenAPITools/Client/ApiClient.cs b/samples/client/petstore/csharp-netcore/OpenAPIClientCore/src/Org.OpenAPITools/Client/ApiClient.cs index 55b5d137d6fb..db5a263b6394 100644 --- a/samples/client/petstore/csharp-netcore/OpenAPIClientCore/src/Org.OpenAPITools/Client/ApiClient.cs +++ b/samples/client/petstore/csharp-netcore/OpenAPIClientCore/src/Org.OpenAPITools/Client/ApiClient.cs @@ -331,25 +331,40 @@ private RestRequest NewRequest( if (options.Data != null) { - if (options.HeaderParameters != null) + if (options.Data is Stream stream) { - var contentTypes = options.HeaderParameters["Content-Type"]; - if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + var contentType = "application/octet-stream"; + if (options.HeaderParameters != null) { - request.RequestFormat = DataFormat.Json; - } - else - { - // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + var contentTypes = options.HeaderParameters["Content-Type"]; + contentType = contentTypes[0]; } + + var bytes = ClientUtils.ReadAsBytes(stream); + request.AddParameter(contentType, bytes, ParameterType.RequestBody); } else { - // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. - request.RequestFormat = DataFormat.Json; - } + if (options.HeaderParameters != null) + { + var contentTypes = options.HeaderParameters["Content-Type"]; + if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) + { + request.RequestFormat = DataFormat.Json; + } + else + { + // TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default. + } + } + else + { + // Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. + request.RequestFormat = DataFormat.Json; + } - request.AddJsonBody(options.Data); + request.AddJsonBody(options.Data); + } } if (options.FileParameters != null)