RicoSuter/NSwag

AspNetCoreToSwaggerGenerator produces one "consumes" output when multiple are defined on the controller

Open

#1,765 opened on Nov 22, 2018

View on GitHub
 (1 comment) (0 reactions) (0 assignees)C# (1,189 forks)batch import
help wantedproject: NSwag.AspNetCore

Repository metrics

Stars
 (6,291 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

I have a controller method that takes a Stream in the body and defines multiple ConsumesAttribute.ContentTypes:

public class FileController : Controller
{
	[HttpPost("customer/{customerId}/file", Name = "UploadFile")]
	[SwaggerResponse(StatusCodes.Status201Created, typeof(UploadFileResponse))]
	[Consumes("application/octet-stream", new string[] { "application/pdf", "image/jpg", "image/jpeg", "image/png", "image/tiff", "image/tif" })]
	public async Task<IActionResult> UploadFile([FromBody] Stream file, [FromRoute] string customerId, [FromQuery] FileQueryParameters queryParameters)
	{
		// file processing here
	}
}

In my startup I'm suppressing Stream model binding, and I have a custom input formatter for a [FromBody] Stream parameter:

public class StreamInputFormatter : IInputFormatter
{
	private readonly List<string> _allowedMimeTypes = new List<string>
		{ "application/pdf", "image/jpg", "image/jpeg", "image/png", "image/tiff", "image/tif", "application/octet-stream" };

	public bool CanRead(InputFormatterContext context)
	{
		if (context == null)
		{
			throw new ArgumentNullException(nameof(context));
		}
		var contentType = context.HttpContext.Request.ContentType;
		if (_allowedMimeTypes.Any(x => x.Contains(contentType)))
		{
			return true;
		}
		if (contentType == "application/octet-stream")
		{
			return true;
		}
		return false;
	}

	public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
	{
		if (context == null)
		{
			throw new ArgumentNullException(nameof(context));
		}

		var req = context.HttpContext.Request;
		req.EnableRewind();

		var memoryStream = new MemoryStream();
		context.HttpContext.Request.Body.CopyTo(memoryStream);
		req.Body.Seek(0, SeekOrigin.Begin);
		return InputFormatterResult.SuccessAsync(memoryStream);
	}
}

They are added the ServicesCollection:

services.AddMvc(options =>
{
	options.InputFormatters.Add(new StreamInputFormatter());
	options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Stream)));
}

Swagger initialized as:

services.AddSwaggerDocument(document =>
{
	document.DocumentName = "swagger";
	document.DocumentProcessors.Add(new SecurityDefinitionAppender("Authorization", new SwaggerSecurityScheme
	{
		Type = SwaggerSecuritySchemeType.ApiKey,
		Name = "Authorization",
		In = SwaggerSecurityApiKeyLocation.Header,
		Description = "Authorization"
	}));
	document.PostProcess = (d) =>
	{
		d.Info.Title = "File API";
		d.Schemes.Clear();
		d.Schemes.Add(SwaggerSchema.Http);
		d.Schemes.Add(SwaggerSchema.Https);
	};
});
	
app.UseSwagger(options =>
{
	options.DocumentName = "swagger";
	options.Path = "/swagger/v1/swagger.json";
});

app.UseSwaggerUi3(options =>
{
	options.Path = "/swagger";
	options.DocumentPath = "/swagger/v1/swagger.json";
});

The output of the swagger.json related to this method looks like so:

"post": {
	"tags": [
	  "File"
	],
	"operationId": "File_UploadFile",
	"consumes": [
	  "application/octet-stream" **<-- expecting more than one consumes**
	],
	"parameters": [
	  {
		"name": "file",
		"in": "body",
		"required": true,
		"schema": {
		  "type": "string",
		  "format": "byte",
		  "x-nullable": false
		},
		"x-nullable": false
	  },
	  {
		"type": "string",
		"name": "customerId",
		"in": "path",
		"required": true,
		"x-nullable": false
	  },
	  {
		"type": "string",
		"name": "filename",
		"in": "query",
		"x-nullable": true
	  },
	  {
		"type": "integer",
		"name": "documentType",
		"in": "query",
		"x-schema": {
		  "$ref": "#/definitions/DocumentType"
		},
		"x-nullable": false,
		"enum": [
		  0,
		  1,
		  2,
		  3
		]
	  },
	  {
		"type": "string",
		"name": "versionNumber",
		"in": "path",
		"required": true,
		"x-nullable": false
	  },
	  {
		"type": "string",
		"name": "tenantId",
		"in": "path",
		"required": true,
		"x-nullable": false
	  }
	],
	"responses": {
	  "201": {
		"x-nullable": true,
		"description": "",
		"schema": {
		  "$ref": "#/definitions/UploadFileResponse"
		}
	  }
	}
}

The expected output was:

"post": {
	"tags": [
	  "File"
	],
	"operationId": "File_UploadFile",
	"consumes": [
	  "application/octet-stream", 
	  "application/pdf", "image/jpg", 
	  "image/jpeg", 
	  "image/png", 
	  "image/tiff", 
	  "image/tif"
	],
	"parameters": [
	  {
		"name": "file",
		"in": "body",
		"required": true,
		"schema": {
		  "type": "string",
		  "format": "byte",
		  "x-nullable": false
		},
		"x-nullable": false
	  },
	  {
		"type": "string",
		"name": "customerId",
		"in": "path",
		"required": true,
		"x-nullable": false
	  },
	  {
		"type": "string",
		"name": "filename",
		"in": "query",
		"x-nullable": true
	  },
	  {
		"type": "integer",
		"name": "documentType",
		"in": "query",
		"x-schema": {
		  "$ref": "#/definitions/DocumentType"
		},
		"x-nullable": false,
		"enum": [
		  0,
		  1,
		  2,
		  3
		]
	  },
	  {
		"type": "string",
		"name": "versionNumber",
		"in": "path",
		"required": true,
		"x-nullable": false
	  },
	  {
		"type": "string",
		"name": "tenantId",
		"in": "path",
		"required": true,
		"x-nullable": false
	  }
	],
	"responses": {
	  "201": {
		"x-nullable": true,
		"description": "",
		"schema": {
		  "$ref": "#/definitions/UploadFileResponse"
		}
	  }
	}
}

The generated client uses the StreamContent, but forces "application/octet-stream". There is no way to override it that I can see?

....
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
	var content_ = new System.Net.Http.StreamContent(file);
	content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/octet-stream");
	request_.Content = content_;
	request_.Method = new System.Net.Http.HttpMethod("POST");
	....
}

I would like to support multiple values for "consumes", for a variety of different file types, but only one consumes is outputted.

Is this possible somehow?

Contributor guide