[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
This commit is contained in:
Blackclaws 2021-03-19 18:01:21 +01:00 committed by GitHub
parent 46326249d2
commit 88fa5d70d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 244 additions and 131 deletions

View File

@ -331,25 +331,40 @@ namespace {{packageName}}.Client
if (options.Data != null) if (options.Data != null)
{ {
if (options.HeaderParameters != null) if (options.Data is Stream stream)
{ {
var contentTypes = options.HeaderParameters["Content-Type"]; var contentType = "application/octet-stream";
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) if (options.HeaderParameters != null)
{ {
request.RequestFormat = DataFormat.Json; var contentTypes = options.HeaderParameters["Content-Type"];
} contentType = contentTypes[0];
else
{
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
} }
var bytes = ClientUtils.ReadAsBytes(stream);
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
} }
else else
{ {
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. if (options.HeaderParameters != null)
request.RequestFormat = DataFormat.Json; {
} 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) if (options.FileParameters != null)

View File

@ -24,6 +24,7 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
using System.Net.Http; using System.Net.Http;
{{/useWebRequest}} {{/useWebRequest}}
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
{{#supportsRetry}} {{#supportsRetry}}
using Polly; using Polly;
{{/supportsRetry}} {{/supportsRetry}}
@ -214,6 +215,32 @@ namespace {{packageName}}.Client
{{/reUseHttpClient}} {{/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;
}
/// <summary> /// <summary>
/// Provides all logic for constructing a new HttpRequestMessage. /// Provides all logic for constructing a new HttpRequestMessage.
/// At this point, all information for querying the service is known. Here, it is simply /// At this point, all information for querying the service is known. Here, it is simply
@ -270,52 +297,45 @@ namespace {{packageName}}.Client
List<Tuple<HttpContent, string, string>> contentList = new List<Tuple<HttpContent, string, string>>(); List<Tuple<HttpContent, string, string>> contentList = new List<Tuple<HttpContent, string, string>>();
if (options.FormParameters != null && options.FormParameters.Count > 0) string contentType = null;
if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type"))
{ {
contentList.Add(new Tuple<HttpContent, string, string>(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); request.Content = PrepareMultipartFormDataContent(options);
contentList.Add(
new Tuple<HttpContent, string, string>(new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), "application/json"), null, null));
} }
else if (contentType == "application/x-www-form-urlencoded")
if (options.FileParameters != null && options.FileParameters.Count > 0)
{ {
foreach (var fileParam in options.FileParameters) request.Content = new FormUrlEncodedContent(options.FormParameters);
{
var bytes = ClientUtils.ReadAsBytes(fileParam.Value);
var fileStream = fileParam.Value as FileStream;
contentList.Add(new Tuple<HttpContent, string, string>(new ByteArrayContent(bytes), fileParam.Key,
fileStream?.Name ?? "no_file_name_provided"));
}
}
if (contentList.Count > 1)
{
string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant();
var multipartContent = new MultipartFormDataContent(boundary);
foreach (var content in contentList)
{
if(content.Item2 != null)
{
multipartContent.Add(content.Item1, content.Item2, content.Item3);
}
else
{
multipartContent.Add(content.Item1);
}
}
request.Content = multipartContent;
} }
else else
{ {
request.Content = contentList.FirstOrDefault()?.Item1; if (options.Data != null)
{
if (options.Data is Stream s)
{
contentType ??= "application/octet-stream";
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 // TODO provide an alternative that allows cookies per request instead of per API client
if (options.Cookies != null && options.Cookies.Count > 0) if (options.Cookies != null && options.Cookies.Count > 0)
{ {

View File

@ -284,7 +284,7 @@ namespace Org.OpenAPITools.Test
/// <summary> /// <summary>
/// Test UploadFile /// Test UploadFile
/// </summary> /// </summary>
[Fact (Skip = "file upload not working for httpclient yet")] [Fact]
public void UploadFileTest() public void UploadFileTest()
{ {
Assembly _assembly = Assembly.GetExecutingAssembly(); Assembly _assembly = Assembly.GetExecutingAssembly();

View File

@ -26,6 +26,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using Polly; using Polly;
namespace Org.OpenAPITools.Client namespace Org.OpenAPITools.Client
@ -202,6 +203,31 @@ namespace Org.OpenAPITools.Client
_baseUrl = 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;
}
/// <summary> /// <summary>
/// Provides all logic for constructing a new HttpRequestMessage. /// Provides all logic for constructing a new HttpRequestMessage.
/// At this point, all information for querying the service is known. Here, it is simply /// At this point, all information for querying the service is known. Here, it is simply
@ -258,52 +284,44 @@ namespace Org.OpenAPITools.Client
List<Tuple<HttpContent, string, string>> contentList = new List<Tuple<HttpContent, string, string>>(); List<Tuple<HttpContent, string, string>> contentList = new List<Tuple<HttpContent, string, string>>();
if (options.FormParameters != null && options.FormParameters.Count > 0) string contentType = null;
if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type"))
{ {
contentList.Add(new Tuple<HttpContent, string, string>(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); request.Content = PrepareMultipartFormDataContent(options);
contentList.Add(
new Tuple<HttpContent, string, string>(new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), "application/json"), null, null));
} }
else if (contentType == "application/x-www-form-urlencoded")
if (options.FileParameters != null && options.FileParameters.Count > 0)
{ {
foreach (var fileParam in options.FileParameters) request.Content = new FormUrlEncodedContent(options.FormParameters);
{
var bytes = ClientUtils.ReadAsBytes(fileParam.Value);
var fileStream = fileParam.Value as FileStream;
contentList.Add(new Tuple<HttpContent, string, string>(new ByteArrayContent(bytes), fileParam.Key,
fileStream?.Name ?? "no_file_name_provided"));
}
}
if (contentList.Count > 1)
{
string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant();
var multipartContent = new MultipartFormDataContent(boundary);
foreach (var content in contentList)
{
if(content.Item2 != null)
{
multipartContent.Add(content.Item1, content.Item2, content.Item3);
}
else
{
multipartContent.Add(content.Item1);
}
}
request.Content = multipartContent;
} }
else else
{ {
request.Content = contentList.FirstOrDefault()?.Item1; if (options.Data != null)
{
if (options.Data is Stream s)
{
contentType ??= "application/octet-stream";
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 // TODO provide an alternative that allows cookies per request instead of per API client
if (options.Cookies != null && options.Cookies.Count > 0) if (options.Cookies != null && options.Cookies.Count > 0)
{ {

View File

@ -331,25 +331,40 @@ namespace Org.OpenAPITools.Client
if (options.Data != null) if (options.Data != null)
{ {
if (options.HeaderParameters != null) if (options.Data is Stream stream)
{ {
var contentTypes = options.HeaderParameters["Content-Type"]; var contentType = "application/octet-stream";
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) if (options.HeaderParameters != null)
{ {
request.RequestFormat = DataFormat.Json; var contentTypes = options.HeaderParameters["Content-Type"];
} contentType = contentTypes[0];
else
{
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
} }
var bytes = ClientUtils.ReadAsBytes(stream);
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
} }
else else
{ {
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. if (options.HeaderParameters != null)
request.RequestFormat = DataFormat.Json; {
} 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) if (options.FileParameters != null)

View File

@ -331,25 +331,40 @@ namespace Org.OpenAPITools.Client
if (options.Data != null) if (options.Data != null)
{ {
if (options.HeaderParameters != null) if (options.Data is Stream stream)
{ {
var contentTypes = options.HeaderParameters["Content-Type"]; var contentType = "application/octet-stream";
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) if (options.HeaderParameters != null)
{ {
request.RequestFormat = DataFormat.Json; var contentTypes = options.HeaderParameters["Content-Type"];
} contentType = contentTypes[0];
else
{
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
} }
var bytes = ClientUtils.ReadAsBytes(stream);
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
} }
else else
{ {
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. if (options.HeaderParameters != null)
request.RequestFormat = DataFormat.Json; {
} 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) if (options.FileParameters != null)

View File

@ -330,25 +330,40 @@ namespace Org.OpenAPITools.Client
if (options.Data != null) if (options.Data != null)
{ {
if (options.HeaderParameters != null) if (options.Data is Stream stream)
{ {
var contentTypes = options.HeaderParameters["Content-Type"]; var contentType = "application/octet-stream";
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) if (options.HeaderParameters != null)
{ {
request.RequestFormat = DataFormat.Json; var contentTypes = options.HeaderParameters["Content-Type"];
} contentType = contentTypes[0];
else
{
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
} }
var bytes = ClientUtils.ReadAsBytes(stream);
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
} }
else else
{ {
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. if (options.HeaderParameters != null)
request.RequestFormat = DataFormat.Json; {
} 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) if (options.FileParameters != null)

View File

@ -331,25 +331,40 @@ namespace Org.OpenAPITools.Client
if (options.Data != null) if (options.Data != null)
{ {
if (options.HeaderParameters != null) if (options.Data is Stream stream)
{ {
var contentTypes = options.HeaderParameters["Content-Type"]; var contentType = "application/octet-stream";
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json"))) if (options.HeaderParameters != null)
{ {
request.RequestFormat = DataFormat.Json; var contentTypes = options.HeaderParameters["Content-Type"];
} contentType = contentTypes[0];
else
{
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
} }
var bytes = ClientUtils.ReadAsBytes(stream);
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
} }
else else
{ {
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly. if (options.HeaderParameters != null)
request.RequestFormat = DataFormat.Json; {
} 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) if (options.FileParameters != null)