Skip to content

Commit 88fa5d7

Browse files
authored
[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
1 parent 4632624 commit 88fa5d7

8 files changed

Lines changed: 242 additions & 129 deletions

File tree

modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -331,25 +331,40 @@ namespace {{packageName}}.Client
331331

332332
if (options.Data != null)
333333
{
334-
if (options.HeaderParameters != null)
334+
if (options.Data is Stream stream)
335335
{
336-
var contentTypes = options.HeaderParameters["Content-Type"];
337-
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json")))
336+
var contentType = "application/octet-stream";
337+
if (options.HeaderParameters != null)
338338
{
339-
request.RequestFormat = DataFormat.Json;
340-
}
341-
else
342-
{
343-
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
339+
var contentTypes = options.HeaderParameters["Content-Type"];
340+
contentType = contentTypes[0];
344341
}
342+
343+
var bytes = ClientUtils.ReadAsBytes(stream);
344+
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
345345
}
346346
else
347347
{
348-
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly.
349-
request.RequestFormat = DataFormat.Json;
350-
}
348+
if (options.HeaderParameters != null)
349+
{
350+
var contentTypes = options.HeaderParameters["Content-Type"];
351+
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json")))
352+
{
353+
request.RequestFormat = DataFormat.Json;
354+
}
355+
else
356+
{
357+
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
358+
}
359+
}
360+
else
361+
{
362+
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly.
363+
request.RequestFormat = DataFormat.Json;
364+
}
351365
352-
request.AddJsonBody(options.Data);
366+
request.AddJsonBody(options.Data);
367+
}
353368
}
354369
355370
if (options.FileParameters != null)

modules/openapi-generator/src/main/resources/csharp-netcore/libraries/httpclient/ApiClient.mustache

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
2424
using System.Net.Http;
2525
{{/useWebRequest}}
2626
using System.Net.Http;
27+
using System.Net.Http.Headers;
2728
{{#supportsRetry}}
2829
using Polly;
2930
{{/supportsRetry}}
@@ -214,6 +215,32 @@ namespace {{packageName}}.Client
214215
{{/reUseHttpClient}}
215216
}
216217

218+
/// Prepares multipart/form-data content
219+
{{! TODO: Add handling of improper usage }}
220+
HttpContent PrepareMultipartFormDataContent(RequestOptions options)
221+
{
222+
string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant();
223+
var multipartContent = new MultipartFormDataContent(boundary);
224+
foreach (var formParameter in options.FormParameters)
225+
{
226+
multipartContent.Add(new StringContent(formParameter.Value), formParameter.Key);
227+
}
228+
229+
if (options.FileParameters != null && options.FileParameters.Count > 0)
230+
{
231+
foreach (var fileParam in options.FileParameters)
232+
{
233+
var fileStream = fileParam.Value as FileStream;
234+
var fileStreamName = fileStream != null ? System.IO.Path.GetFileName(fileStream.Name) : null;
235+
var content = new StreamContent(fileParam.Value);
236+
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
237+
multipartContent.Add(content, fileParam.Key,
238+
fileStreamName ?? "no_file_name_provided");
239+
}
240+
}
241+
return multipartContent;
242+
}
243+
217244
/// <summary>
218245
/// Provides all logic for constructing a new HttpRequestMessage.
219246
/// At this point, all information for querying the service is known. Here, it is simply
@@ -270,52 +297,45 @@ namespace {{packageName}}.Client
270297

271298
List<Tuple<HttpContent, string, string>> contentList = new List<Tuple<HttpContent, string, string>>();
272299

273-
if (options.FormParameters != null && options.FormParameters.Count > 0)
300+
string contentType = null;
301+
if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type"))
274302
{
275-
contentList.Add(new Tuple<HttpContent, string, string>(new FormUrlEncodedContent(options.FormParameters), null, null));
303+
var contentTypes = options.HeaderParameters["Content-Type"];
304+
contentType = contentTypes.FirstOrDefault();
276305
}
277306

278-
if (options.Data != null)
307+
{{!// TODO Add error handling in case of improper usage}}
308+
if (contentType == "multipart/form-data")
279309
{
280-
var serializer = new CustomJsonCodec(SerializerSettings, configuration);
281-
contentList.Add(
282-
new Tuple<HttpContent, string, string>(new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), "application/json"), null, null));
310+
request.Content = PrepareMultipartFormDataContent(options);
283311
}
284-
285-
if (options.FileParameters != null && options.FileParameters.Count > 0)
312+
else if (contentType == "application/x-www-form-urlencoded")
286313
{
287-
foreach (var fileParam in options.FileParameters)
288-
{
289-
var bytes = ClientUtils.ReadAsBytes(fileParam.Value);
290-
var fileStream = fileParam.Value as FileStream;
291-
contentList.Add(new Tuple<HttpContent, string, string>(new ByteArrayContent(bytes), fileParam.Key,
292-
fileStream?.Name ?? "no_file_name_provided"));
293-
}
314+
request.Content = new FormUrlEncodedContent(options.FormParameters);
294315
}
295-
296-
if (contentList.Count > 1)
316+
else
297317
{
298-
string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant();
299-
var multipartContent = new MultipartFormDataContent(boundary);
300-
foreach (var content in contentList)
318+
if (options.Data != null)
301319
{
302-
if(content.Item2 != null)
303-
{
304-
multipartContent.Add(content.Item1, content.Item2, content.Item3);
305-
}
306-
else
307-
{
308-
multipartContent.Add(content.Item1);
309-
}
310-
}
320+
if (options.Data is Stream s)
321+
{
322+
contentType ??= "application/octet-stream";
311323
312-
request.Content = multipartContent;
313-
}
314-
else
315-
{
316-
request.Content = contentList.FirstOrDefault()?.Item1;
324+
var streamContent = new StreamContent(s);
325+
streamContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
326+
request.Content = streamContent;
327+
}
328+
else
329+
{
330+
var serializer = new CustomJsonCodec(SerializerSettings, configuration);
331+
request.Content = new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(),
332+
"application/json");
333+
}
334+
}
317335
}
318336

337+
338+
319339
// TODO provide an alternative that allows cookies per request instead of per API client
320340
if (options.Cookies != null && options.Cookies.Count > 0)
321341
{

samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools.Test/Api/PetApiTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ public void UpdatePetWithFormTest()
284284
/// <summary>
285285
/// Test UploadFile
286286
/// </summary>
287-
[Fact (Skip = "file upload not working for httpclient yet")]
287+
[Fact]
288288
public void UploadFileTest()
289289
{
290290
Assembly _assembly = Assembly.GetExecutingAssembly();

samples/client/petstore/csharp-netcore/OpenAPIClient-httpclient/src/Org.OpenAPITools/Client/ApiClient.cs

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using Newtonsoft.Json.Serialization;
2727
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
2828
using System.Net.Http;
29+
using System.Net.Http.Headers;
2930
using Polly;
3031

3132
namespace Org.OpenAPITools.Client
@@ -202,6 +203,31 @@ public ApiClient(String basePath)
202203
_baseUrl = basePath;
203204
}
204205

206+
/// Prepares multipart/form-data content
207+
HttpContent PrepareMultipartFormDataContent(RequestOptions options)
208+
{
209+
string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant();
210+
var multipartContent = new MultipartFormDataContent(boundary);
211+
foreach (var formParameter in options.FormParameters)
212+
{
213+
multipartContent.Add(new StringContent(formParameter.Value), formParameter.Key);
214+
}
215+
216+
if (options.FileParameters != null && options.FileParameters.Count > 0)
217+
{
218+
foreach (var fileParam in options.FileParameters)
219+
{
220+
var fileStream = fileParam.Value as FileStream;
221+
var fileStreamName = fileStream != null ? System.IO.Path.GetFileName(fileStream.Name) : null;
222+
var content = new StreamContent(fileParam.Value);
223+
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
224+
multipartContent.Add(content, fileParam.Key,
225+
fileStreamName ?? "no_file_name_provided");
226+
}
227+
}
228+
return multipartContent;
229+
}
230+
205231
/// <summary>
206232
/// Provides all logic for constructing a new HttpRequestMessage.
207233
/// At this point, all information for querying the service is known. Here, it is simply
@@ -258,52 +284,44 @@ private HttpRequestMessage NewRequest(
258284

259285
List<Tuple<HttpContent, string, string>> contentList = new List<Tuple<HttpContent, string, string>>();
260286

261-
if (options.FormParameters != null && options.FormParameters.Count > 0)
287+
string contentType = null;
288+
if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type"))
262289
{
263-
contentList.Add(new Tuple<HttpContent, string, string>(new FormUrlEncodedContent(options.FormParameters), null, null));
290+
var contentTypes = options.HeaderParameters["Content-Type"];
291+
contentType = contentTypes.FirstOrDefault();
264292
}
265293

266-
if (options.Data != null)
294+
if (contentType == "multipart/form-data")
267295
{
268-
var serializer = new CustomJsonCodec(SerializerSettings, configuration);
269-
contentList.Add(
270-
new Tuple<HttpContent, string, string>(new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(), "application/json"), null, null));
296+
request.Content = PrepareMultipartFormDataContent(options);
271297
}
272-
273-
if (options.FileParameters != null && options.FileParameters.Count > 0)
298+
else if (contentType == "application/x-www-form-urlencoded")
274299
{
275-
foreach (var fileParam in options.FileParameters)
276-
{
277-
var bytes = ClientUtils.ReadAsBytes(fileParam.Value);
278-
var fileStream = fileParam.Value as FileStream;
279-
contentList.Add(new Tuple<HttpContent, string, string>(new ByteArrayContent(bytes), fileParam.Key,
280-
fileStream?.Name ?? "no_file_name_provided"));
281-
}
300+
request.Content = new FormUrlEncodedContent(options.FormParameters);
282301
}
283-
284-
if (contentList.Count > 1)
302+
else
285303
{
286-
string boundary = "---------" + Guid.NewGuid().ToString().ToUpperInvariant();
287-
var multipartContent = new MultipartFormDataContent(boundary);
288-
foreach (var content in contentList)
304+
if (options.Data != null)
289305
{
290-
if(content.Item2 != null)
291-
{
292-
multipartContent.Add(content.Item1, content.Item2, content.Item3);
293-
}
294-
else
295-
{
296-
multipartContent.Add(content.Item1);
297-
}
298-
}
306+
if (options.Data is Stream s)
307+
{
308+
contentType ??= "application/octet-stream";
299309

300-
request.Content = multipartContent;
301-
}
302-
else
303-
{
304-
request.Content = contentList.FirstOrDefault()?.Item1;
310+
var streamContent = new StreamContent(s);
311+
streamContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
312+
request.Content = streamContent;
313+
}
314+
else
315+
{
316+
var serializer = new CustomJsonCodec(SerializerSettings, configuration);
317+
request.Content = new StringContent(serializer.Serialize(options.Data), new UTF8Encoding(),
318+
"application/json");
319+
}
320+
}
305321
}
306322

323+
324+
307325
// TODO provide an alternative that allows cookies per request instead of per API client
308326
if (options.Cookies != null && options.Cookies.Count > 0)
309327
{

samples/client/petstore/csharp-netcore/OpenAPIClient-net47/src/Org.OpenAPITools/Client/ApiClient.cs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -331,25 +331,40 @@ private RestRequest NewRequest(
331331

332332
if (options.Data != null)
333333
{
334-
if (options.HeaderParameters != null)
334+
if (options.Data is Stream stream)
335335
{
336-
var contentTypes = options.HeaderParameters["Content-Type"];
337-
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json")))
336+
var contentType = "application/octet-stream";
337+
if (options.HeaderParameters != null)
338338
{
339-
request.RequestFormat = DataFormat.Json;
340-
}
341-
else
342-
{
343-
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
339+
var contentTypes = options.HeaderParameters["Content-Type"];
340+
contentType = contentTypes[0];
344341
}
342+
343+
var bytes = ClientUtils.ReadAsBytes(stream);
344+
request.AddParameter(contentType, bytes, ParameterType.RequestBody);
345345
}
346346
else
347347
{
348-
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly.
349-
request.RequestFormat = DataFormat.Json;
350-
}
348+
if (options.HeaderParameters != null)
349+
{
350+
var contentTypes = options.HeaderParameters["Content-Type"];
351+
if (contentTypes == null || contentTypes.Any(header => header.Contains("application/json")))
352+
{
353+
request.RequestFormat = DataFormat.Json;
354+
}
355+
else
356+
{
357+
// TODO: Generated client user should add additional handlers. RestSharp only supports XML and JSON, with XML as default.
358+
}
359+
}
360+
else
361+
{
362+
// Here, we'll assume JSON APIs are more common. XML can be forced by adding produces/consumes to openapi spec explicitly.
363+
request.RequestFormat = DataFormat.Json;
364+
}
351365

352-
request.AddJsonBody(options.Data);
366+
request.AddJsonBody(options.Data);
367+
}
353368
}
354369

355370
if (options.FileParameters != null)

0 commit comments

Comments
 (0)