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

Throw an error when a user uses FromForm attribute with IFormFile in … #2840

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -5,12 +5,14 @@
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Swagger;

#if NET7_0_OR_GREATER
using Microsoft.AspNetCore.Http.Metadata;
#endif
Expand Down Expand Up @@ -328,6 +330,12 @@ private IList<OpenApiTag> GenerateOperationTags(ApiDescription apiDescription)

private IList<OpenApiParameter> GenerateParameters(ApiDescription apiDescription, SchemaRepository schemaRespository)
{
if (apiDescription.ParameterDescriptions.Any(apiParam => IsFromFormAttributeUsedWithIFormFile(apiParam)))
martincostello marked this conversation as resolved.
Show resolved Hide resolved
throw new SwaggerGeneratorException(string.Format(
"Error reading parameter(s) for action {0} as [FromForm] attribute used with IFormFile. " +
"Please refer to https://github.com/domaindrivendev/Swashbuckle.AspNetCore#handle-forms-and-file-uploads for more information",
apiDescription.ActionDescriptor.DisplayName));

var applicableApiParameters = apiDescription.ParameterDescriptions
.Where(apiParam =>
{
Expand Down Expand Up @@ -626,6 +634,17 @@ private OpenApiMediaType CreateResponseMediaType(ModelMetadata modelMetadata, Sc
};
}

private static bool IsFromFormAttributeUsedWithIFormFile(ApiParameterDescription apiParameter)
{
// Retrieve parameter information
var parameterInfo = apiParameter.ParameterInfo();

// Check if Parameter has FromForm Attribute
var fromFormAttribute = parameterInfo?.GetCustomAttribute<FromFormAttribute>();

return fromFormAttribute != null && parameterInfo?.ParameterType == typeof(IFormFile);
martincostello marked this conversation as resolved.
Show resolved Hide resolved
}

private static readonly Dictionary<string, OperationType> OperationTypeMap = new Dictionary<string, OperationType>
{
{ "GET", OperationType.Get },
Expand Down
@@ -1,6 +1,7 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Swashbuckle.AspNetCore.Annotations;
Expand Down Expand Up @@ -84,5 +85,11 @@ public int ActionWithProducesAttribute()
[SwaggerIgnore]
public void ActionWithSwaggerIgnoreAttribute()
{ }

public void ActionHavingIFormFileParamWithFromFormAtribute([FromForm] IFormFile fileUpload)
{ }

public void ActionHavingFromFormAtributeButNotWithIFormFile([FromForm] string param1, IFormFile param2)
{ }
}
}
Expand Up @@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing;
using Microsoft.OpenApi.Any;
Expand Down Expand Up @@ -1433,6 +1434,87 @@ public void GetSwagger_GeneratesSwaggerDocument_ThrowsIfHttpMethodNotSupported(s
Assert.Equal($"The \"{httpMethod}\" HTTP method is not supported.", exception.Message);
}

[Fact]
public void GetSwagger_Throws_Exception_When_FromForm_Attribute_Used_With_IFormFile()
{
var parameterInfo = typeof(FakeController)
.GetMethod(nameof(FakeController.ActionHavingIFormFileParamWithFromFormAtribute))
.GetParameters()[0];


var subject = Subject(
martincostello marked this conversation as resolved.
Show resolved Hide resolved
apiDescriptions: new[]
{
ApiDescriptionFactory.Create<FakeController>(
c => nameof(c.ActionHavingIFormFileParamWithFromFormAtribute),
groupName: "v1",
httpMethod: "POST",
relativePath: "resource",
parameterDescriptions: new[]
{
new ApiParameterDescription
{
Name = "fileUpload", // Name of the parameter
Type = typeof(IFormFile), // Type of the parameter
ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = parameterInfo }
}
})
}
);

var exception = Assert.Throws<SwaggerGeneratorException>(() => subject.GetSwagger("v1"));
martincostello marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void GetSwagger_Works_As_Expected_When_FromForm_Attribute_Not_Used_With_IFormFile()
{
var paraminfo = typeof(FakeController)
.GetMethod(nameof(FakeController.ActionHavingFromFormAtributeButNotWithIFormFile))
.GetParameters()[0];

var fileUploadParameterInfo = typeof(FakeController)
.GetMethod(nameof(FakeController.ActionHavingFromFormAtributeButNotWithIFormFile))
.GetParameters()[1];

var subject = Subject(
apiDescriptions: new[]
{
ApiDescriptionFactory.Create<FakeController>(
c => nameof(c.ActionHavingFromFormAtributeButNotWithIFormFile),
groupName: "v1",
httpMethod: "POST",
relativePath: "resource",
parameterDescriptions: new[]
{
new ApiParameterDescription
{
Name = "param1", // Name of the parameter
Type = typeof(string), // Type of the parameter
ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = paraminfo }
},
new ApiParameterDescription
{
Name = "param2", // Name of the parameter
Type = typeof(IFormFile), // Type of the parameter
ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = fileUploadParameterInfo }
}
})
}
);

var document = subject.GetSwagger("v1");

Assert.Equal("V1", document.Info.Version);
Assert.Equal("Test API", document.Info.Title);
Assert.Equal(new[] { "/resource" }, document.Paths.Keys.ToArray());

var operation = document.Paths["/resource"].Operations[OperationType.Post];
Assert.NotNull(operation.Parameters);
Assert.Equal(2, operation.Parameters.Count);
Assert.Equal("param1", operation.Parameters[0].Name);
Assert.Equal("param2", operation.Parameters[1].Name);
}

private static SwaggerGenerator Subject(
IEnumerable<ApiDescription> apiDescriptions,
SwaggerGeneratorOptions options = null,
Expand Down