Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[csharp][aspnetcore] Fix FileUpload Parameter Generation for aspnetcore #9000

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;

Expand Down Expand Up @@ -398,6 +399,7 @@ public void processOpts() {
}

supportingFiles.add(new SupportingFile("Authentication" + File.separator + "ApiAuthentication.mustache", packageFolder + File.separator + "Authentication", "ApiAuthentication.cs"));
supportingFiles.add(new SupportingFile("Formatters" + File.separator + "InputFormatterStream.mustache", packageFolder + File.separator + "Formatters", "InputFormatterStream.cs"));
}

public void setPackageGuid(String packageGuid) {
Expand Down Expand Up @@ -437,6 +439,68 @@ protected void processOperation(CodegenOperation operation) {
operation.httpMethod = "Http" + operation.httpMethod.substring(0, 1) + operation.httpMethod.substring(1).toLowerCase(Locale.ROOT);
}

@Override
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
super.postProcessOperationsWithModels(objs, allModels);
// We need to postprocess the operations to add proper consumes tags and fix form file handling
if (objs != null) {
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
if (operations != null) {
List<CodegenOperation> ops = (List<CodegenOperation>) operations.get("operation");
for (CodegenOperation operation : ops) {
if (operation.consumes == null) {
break;
}
if (operation.consumes.size() == 0) {
break;
}

// Build a consumes string for the operation we cannot iterate in the template as we need a ','
// after each entry but the last

StringBuilder consumesString = new StringBuilder();
for (Map<String, String> consume : operation.consumes) {
if (!consume.containsKey("mediaType")) {
continue;
}

if(consumesString.toString().equals("")) {
consumesString = new StringBuilder("\"" + consume.get("mediaType") + "\"");
}
else {
consumesString.append(", \"").append(consume.get("mediaType")).append("\"");
}

// In a multipart/form-data consuming context binary data is best handled by an IFormFile
if (!consume.get("mediaType").equals("multipart/form-data")) {
continue;
}

// Change dataType of binary parameters to IFormFile for formParams in multipart/form-data
for (CodegenParameter param : operation.formParams) {
if (param.isBinary) {
param.dataType = "IFormFile";
param.baseType = "IFormFile";
}
}

for (CodegenParameter param : operation.allParams) {
if (param.isBinary && param.isFormParam) {
param.dataType = "IFormFile";
param.baseType = "IFormFile";
}
}
}

if(!consumesString.toString().equals("")) {
operation.vendorExtensions.put("x-aspnetcore-consumes", consumesString.toString());
}
}
}
}
return objs;
}

@Override
public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
// To avoid unexpected behaviors when options are passed programmatically such as { "useCollection": "" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace {{packageName}}.Formatters
{
// Input Type Formatter to allow model binding to Streams
public class InputFormatterStream : InputFormatter
{
public InputFormatterStream()
{
SupportedMediaTypes.Add("application/octet-stream");
SupportedMediaTypes.Add("image/jpeg");
}

protected override bool CanReadType(Type type)
{
if (type == typeof(Stream))
{
return true;
}

return false;
}

public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
return InputFormatterResult.SuccessAsync(context.HttpContext.Request.Body);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using Newtonsoft.Json.Serialization;{{#useSwashbuckle}}
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using {{packageName}}.Filters;{{/useSwashbuckle}}
using {{packageName}}.Formatters;

namespace {{packageName}}
{
Expand Down Expand Up @@ -40,7 +41,9 @@ namespace {{packageName}}
{
// Add framework services.
services
.AddMvc()
.AddMvc(options => {
options.InputFormatters.Insert(0, new InputFormatterStream());
})
.AddJsonOptions(opts =>
{
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isBodyParam}}[FromBody]{{&dataType}} {{paramName}}{{/isBodyParam}}
{{#isBodyParam}}{{^isBinary}}[FromBody]{{/isBinary}}{{&dataType}} {{paramName}}{{/isBodyParam}}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
{{#useSwashbuckle}}
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.Http;
{{/useSwashbuckle}}
using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations;
Expand All @@ -26,6 +27,9 @@ namespace {{packageName}}.Controllers
/// <response code="{{code}}">{{message}}</response>{{/responses}}
[{{httpMethod}}]
[Route("{{{basePathWithoutHost}}}{{{path}}}")]
{{#vendorExtensions.x-aspnetcore-consumes}}
[Consumes({{&vendorExtensions.x-aspnetcore-consumes}})]
{{/vendorExtensions.x-aspnetcore-consumes}}
[ValidateModelState]{{#useSwashbuckle}}
[SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}}
[SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isFormParam}}[FromForm (Name = "{{baseName}}")]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}}
{{#isFormParam}}{{^isBinary}}[FromForm (Name = "{{baseName}}")]{{/isBinary}}{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}})]{{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace {{packageName}}.Formatters
{
// Input Type Formatter to allow model binding to Streams
public class InputFormatterStream : InputFormatter
{
public InputFormatterStream()
{
SupportedMediaTypes.Add("application/octet-stream");
SupportedMediaTypes.Add("image/jpeg");
}

protected override bool CanReadType(Type type)
{
if (type == typeof(Stream))
{
return true;
}

return false;
}

public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
return InputFormatterResult.SuccessAsync(context.HttpContext.Request.Body);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using {{packageName}}.Filters;{{/useSwashbuckle}}
using {{packageName}}.Authentication;
using {{packageName}}.Formatters;
using Microsoft.AspNetCore.Authorization;

namespace {{packageName}}
Expand Down Expand Up @@ -56,7 +57,10 @@ namespace {{packageName}}

// Add framework services.
services
.AddMvc({{^useDefaultRouting}}opts => opts.EnableEndpointRouting = false{{/useDefaultRouting}})
.AddMvc(opts => {
{{^useDefaultRouting}}opts.EnableEndpointRouting = false;{{/useDefaultRouting}}
opts.InputFormatters.Insert(0, new InputFormatterStream());
})
{{#compatibilityVersion}}
.SetCompatibilityVersion(CompatibilityVersion.{{compatibilityVersion}})
{{/compatibilityVersion}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isBodyParam}}[FromBody]{{&dataType}} {{paramName}}{{/isBodyParam}}
{{#isBodyParam}}{{^isBinary}}[FromBody]{{/isBinary}}{{&dataType}} {{paramName}}{{/isBodyParam}}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
{{/operationResultTask}}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
{{#useSwashbuckle}}
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;
Expand Down Expand Up @@ -42,6 +43,9 @@ namespace {{apiPackage}}
[Authorize{{#scopes}}{{#-first}}(Roles = "{{/-first}}{{scope}}{{^-last}},{{/-last}}{{#-last}}"){{/-last}}{{/scopes}}]
{{/isBasicBearer}}
{{/authMethods}}
{{#vendorExtensions.x-aspnetcore-consumes}}
[Consumes({{&vendorExtensions.x-aspnetcore-consumes}})]
{{/vendorExtensions.x-aspnetcore-consumes}}
[ValidateModelState]{{#useSwashbuckle}}
[SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}}
[SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}}{{^useSwashbuckle}}{{#responses}}{{#dataType}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isFormParam}}[FromForm (Name = "{{baseName}}")]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}})]{{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}}
{{#isFormParam}}{{^isBinary}}[FromForm (Name = "{{baseName}}")]{{/isBinary}}{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}})]{{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace {{packageName}}.Formatters
{
// Input Type Formatter to allow model binding to Streams
public class InputFormatterStream : InputFormatter
{
public InputFormatterStream()
{
SupportedMediaTypes.Add("application/octet-stream");
SupportedMediaTypes.Add("image/jpeg");
}

protected override bool CanReadType(Type type)
{
if (type == typeof(Stream))
{
return true;
}

return false;
}

public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
return InputFormatterResult.SuccessAsync(context.HttpContext.Request.Body);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ using {{packageName}}.Authentication;
using {{packageName}}.Filters;
{{/useSwashbuckle}}
using {{packageName}}.OpenApi;
using {{packageName}}.Formatters;

namespace {{packageName}}
{
Expand Down Expand Up @@ -65,7 +66,9 @@ namespace {{packageName}}
// Add framework services.
services
// Don't need the full MVC stack for an API, see https://andrewlock.net/comparing-startup-between-the-asp-net-core-3-templates/
.AddControllers()
.AddControllers(options => {
options.InputFormatters.Insert(0, new InputFormatterStream());
})
{{#compatibilityVersion}}
// Don't need this for 3.x - see https://docs.microsoft.com/en-us/aspnet/core/mvc/compatibility-version?view=aspnetcore-3.1
//.SetCompatibilityVersion(CompatibilityVersion.{{compatibilityVersion}})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
{{/operationResultTask}}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
{{#useSwashbuckle}}
using Swashbuckle.AspNetCore.Annotations;
using Microsoft.AspNetCore.Authorization;
Expand Down Expand Up @@ -42,6 +43,9 @@ namespace {{apiPackage}}
[Authorize{{#scopes}}{{#-first}}(Roles = "{{/-first}}{{scope}}{{^-last}},{{/-last}}{{#-last}}"){{/-last}}{{/scopes}}]
{{/isBasicBearer}}
{{/authMethods}}
{{#vendorExtensions.x-aspnetcore-consumes}}
[Consumes({{&vendorExtensions.x-aspnetcore-consumes}})]
{{/vendorExtensions.x-aspnetcore-consumes}}
[ValidateModelState]{{#useSwashbuckle}}
[SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}}
[SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}}{{^useSwashbuckle}}{{#responses}}{{#dataType}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isFormParam}}[FromForm (Name = "{{baseName}}")]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}})]{{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}}
{{#isFormParam}}{{^isBinary}}[FromForm (Name = "{{baseName}}")]{{/isBinary}}{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}})]{{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ src/Org.OpenAPITools/Converters/CustomEnumConverter.cs
src/Org.OpenAPITools/Dockerfile
src/Org.OpenAPITools/Filters/BasePathFilter.cs
src/Org.OpenAPITools/Filters/GeneratePathParamsValidationFilter.cs
src/Org.OpenAPITools/Formatters/InputFormatterStream.cs
src/Org.OpenAPITools/Models/ApiResponse.cs
src/Org.OpenAPITools/Models/Category.cs
src/Org.OpenAPITools/Models/Order.cs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Swashbuckle.AspNetCore.Annotations;
using Microsoft.AspNetCore.Authorization;
using Swashbuckle.AspNetCore.SwaggerGen;
Expand All @@ -34,6 +35,7 @@ public class PetApiController : ControllerBase
/// <response code="405">Invalid input</response>
[HttpPost]
[Route("/v2/pet")]
[Consumes("application/json", "application/xml")]
[ValidateModelState]
[SwaggerOperation("AddPet")]
public virtual IActionResult AddPet([FromBody]Pet body)
Expand Down Expand Up @@ -214,7 +216,7 @@ public virtual IActionResult UpdatePetWithForm([FromRoute (Name = "petId")][Requ
[ValidateModelState]
[SwaggerOperation("UploadFile")]
[SwaggerResponse(statusCode: 200, type: typeof(ApiResponse), description: "successful operation")]
public virtual IActionResult UploadFile([FromRoute (Name = "petId")][Required]long petId, [FromForm (Name = "additionalMetadata")]string additionalMetadata, [FromForm (Name = "file")]System.IO.Stream file)
public virtual IActionResult UploadFile([FromRoute (Name = "petId")][Required]long petId, [FromForm (Name = "additionalMetadata")]string additionalMetadata, System.IO.Stream file)
{

//TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Swashbuckle.AspNetCore.Annotations;
using Microsoft.AspNetCore.Authorization;
using Swashbuckle.AspNetCore.SwaggerGen;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Swashbuckle.AspNetCore.Annotations;
using Microsoft.AspNetCore.Authorization;
using Swashbuckle.AspNetCore.SwaggerGen;
Expand Down
Loading