Skip to content

Commit 3ae688a

Browse files
danielcrennaOleksandr Liakhevych
andauthored
Add support for multiple form files (#34)
* Add support for IFormFile[] / List<FormFile> * Add support for form file binding parameters, add tests * nit: unnecessary namespace * nit: whitespace * Make changes based on feedback * Fix another case of over-prescribing type * Revert unneded changes --------- Co-authored-by: Oleksandr Liakhevych <[email protected],m>
1 parent a52ee23 commit 3ae688a

File tree

4 files changed

+70
-10
lines changed

4 files changed

+70
-10
lines changed

GenerateAspNetCoreClient.Command/ClientModelBuilder.cs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,13 @@ private List<Parameter> GetParameters(ApiDescription apiDescription)
150150
for (int i = 0; i < apiDescription.ParameterDescriptions.Count; i++)
151151
{
152152
var parameterDescription = apiDescription.ParameterDescriptions[i];
153+
var paramType = parameterDescription.ParameterDescriptor?.ParameterType;
153154

154-
if (parameterDescription.ParameterDescriptor?.ParameterType == typeof(CancellationToken))
155+
if (paramType == typeof(CancellationToken))
155156
continue;
156157

157158
// IFormFile
158-
if (parameterDescription.ParameterDescriptor?.ParameterType == typeof(IFormFile))
159+
if (paramType == typeof(IFormFile))
159160
{
160161
var name = parameterDescription.ParameterDescriptor.Name;
161162

@@ -177,12 +178,30 @@ private List<Parameter> GetParameters(ApiDescription apiDescription)
177178
continue;
178179
}
179180

181+
// IEnumerable<IFormFile>
182+
if (paramType != null)
183+
{
184+
if (typeof(IEnumerable<IFormFile>).IsAssignableFrom(paramType))
185+
{
186+
var name = parameterDescription.ParameterDescriptor?.Name;
187+
188+
parametersList.Add(new Parameter(
189+
source: ParameterSource.File,
190+
type: typeof(List<Stream>),
191+
name: parameterDescription.Name,
192+
parameterName: name?.ToCamelCase() ?? "files",
193+
defaultValueLiteral: null));
194+
195+
continue;
196+
}
197+
}
198+
180199
// Form
181200
// API explorer shows form as separate parameters. We want to have single model parameter.
182201
if (parameterDescription.Source == BindingSource.Form)
183202
{
184203
var name = parameterDescription.ParameterDescriptor?.Name ?? "form";
185-
var formType = parameterDescription.ParameterDescriptor?.ParameterType ?? typeof(object);
204+
var formType = paramType ?? typeof(object);
186205

187206
if (formType == typeof(IFormCollection))
188207
{
@@ -202,7 +221,7 @@ private List<Parameter> GetParameters(ApiDescription apiDescription)
202221
.ToArray();
203222

204223
// If form model has file parameters - we have to put it as separate parameters.
205-
if (!sameFormParameters.Any(p => p.Source.Id == "FormFile"))
224+
if (sameFormParameters.All(p => p.Source.Id != "FormFile"))
206225
{
207226
parametersList.Add(new Parameter(
208227
source: ParameterSource.Form,
@@ -274,11 +293,19 @@ private List<Parameter> GetParameters(ApiDescription apiDescription)
274293
? parameterDescription.Name.ToCamelCase()
275294
: (parameterDescription.ParameterDescriptor?.Name ?? parameterDescription.Name).ToCamelCase();
276295

277-
parameterName = new string(parameterName.Where(c => char.IsLetterOrDigit(c)).ToArray());
296+
parameterName = new string(parameterName.Where(char.IsLetterOrDigit).ToArray());
278297

279-
var type = parameterDescription.Source == BindingSource.FormFile
280-
? typeof(Stream)
281-
: parameterDescription.ModelMetadata?.ModelType ?? parameterDescription.Type ?? typeof(string);
298+
Type type;
299+
if (parameterDescription.Source == BindingSource.FormFile)
300+
{
301+
type = typeof(IEnumerable<IFormFile>).IsAssignableFrom(parameterDescription.Type)
302+
? typeof(List<Stream>)
303+
: typeof(Stream);
304+
}
305+
else
306+
{
307+
type = parameterDescription.ModelMetadata?.ModelType ?? parameterDescription.Type;
308+
}
282309

283310
var defaultValue = GetDefaultValueLiteral(parameterDescription, type);
284311

GenerateAspNetCoreClient.Command/GenerateClientCommand.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,17 @@ private static string CreateClient(Client clientModel, HashSet<Type> ambiguousTy
8181
_ => ""
8282
};
8383

84-
var type = p.Source == ParameterSource.File ? "MultipartItem" : p.Type.GetName(ambiguousTypes);
84+
string type;
85+
if (p.Source == ParameterSource.File)
86+
{
87+
bool isEnumerable = p.Type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(p.Type);
88+
type = isEnumerable ? "List<MultipartItem>" : "MultipartItem";
89+
}
90+
else
91+
{
92+
type = p.Type.GetName(ambiguousTypes);
93+
}
94+
8595
var defaultValue = p.DefaultValueLiteral == null ? "" : " = " + p.DefaultValueLiteral;
8696
return $"{attribute}{type} {p.ParameterName}{defaultValue}";
8797
})

Tests/GenerateAspNetCoreClient.Tests/__snapshots__/TestWebApi.Controllers/IWeatherForecastApi.cs.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ namespace Test.Name.Space
2424
[Post("/WeatherForecast/upload")]
2525
Task Upload(MultipartItem uploadedFile);
2626

27+
[Multipart]
28+
[Post("/WeatherForecast/upload-multiple")]
29+
Task Upload(List<MultipartItem> uploadedFiles);
30+
2731
[Get("/WeatherForecast/download")]
2832
Task<Stream> Download();
2933

@@ -46,6 +50,10 @@ namespace Test.Name.Space
4650
[Post("/WeatherForecast/form-with-file")]
4751
Task WithFormWithFileParam(string queryParam = null, MultipartItem formParam = null, string title = null);
4852

53+
[Multipart]
54+
[Post("/WeatherForecast/form-with-files")]
55+
Task WithFormWithFilesParam(string queryParam = null, List<MultipartItem> formParam = null, string title = null);
56+
4957
[Get("/WeatherForecast/record")]
5058
Task<RecordModel> WithRecordModels(Guid? id = null, string name = null);
5159
}

Tests/TestWebApi.Controllers/Controllers/WeatherForecastController.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ public Task Upload(IFormFile uploadedFile)
6262
return null;
6363
}
6464

65+
[HttpPost("upload-multiple")]
66+
public Task Upload(List<IFormFile> uploadedFiles)
67+
{
68+
return null;
69+
}
70+
6571
[HttpGet("download")]
6672
public Task<FileContentResult> Download()
6773
{
@@ -109,6 +115,13 @@ public async Task<ActionResult> WithFormWithFileParam([FromQuery] string queryPa
109115
return Ok();
110116
}
111117

118+
[HttpPost("form-with-files")]
119+
public async Task<ActionResult> WithFormWithFilesParam([FromQuery] string queryParam, [FromForm] FormModelWithMultipleFiles formParam)
120+
{
121+
await Task.Delay(1);
122+
return Ok();
123+
}
124+
112125
[HttpGet("record")]
113126
public async Task<ActionResult<RecordModel>> WithRecordModels([FromQuery] RecordModel record)
114127
{
@@ -118,4 +131,6 @@ public async Task<ActionResult<RecordModel>> WithRecordModels([FromQuery] Record
118131
}
119132

120133

121-
public record FormModelWithFile(IFormFile File, string Title);
134+
public record FormModelWithFile(IFormFile File, string Title);
135+
136+
public record FormModelWithMultipleFiles(IFormFile[] Files, string Title);

0 commit comments

Comments
 (0)