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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
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(IsFromFormAttributeUsedWithIFormFile))
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,14 @@ private OpenApiMediaType CreateResponseMediaType(ModelMetadata modelMetadata, Sc
};
}

private static bool IsFromFormAttributeUsedWithIFormFile(ApiParameterDescription apiParameter)
{
var parameterInfo = apiParameter.ParameterInfo();
var fromFormAttribute = parameterInfo?.GetCustomAttribute<FromFormAttribute>();

return fromFormAttribute != null && parameterInfo?.ParameterType == typeof(IFormFile);
}

private static readonly Dictionary<string, OperationType> OperationTypeMap = new Dictionary<string, OperationType>
{
{ "GET", OperationType.Get },
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
{ }
}
}
Original file line number Diff line number Diff line change
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,85 @@ 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(
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 }
}
})
}
);

Assert.Throws<SwaggerGeneratorException>(() => subject.GetSwagger("v1"));
}

[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
Loading