diff --git a/src/NSwag.Generation.AspNetCore.Tests.Web/Controllers/Parameters/FileUploadController.cs b/src/NSwag.Generation.AspNetCore.Tests.Web/Controllers/Parameters/FileUploadController.cs index d06203a66..49ddb6d8a 100644 --- a/src/NSwag.Generation.AspNetCore.Tests.Web/Controllers/Parameters/FileUploadController.cs +++ b/src/NSwag.Generation.AspNetCore.Tests.Web/Controllers/Parameters/FileUploadController.cs @@ -36,5 +36,21 @@ public class CaseAttachmentModel public IFormFile Contents { get; set; } } + + [HttpPost("UploadAttachment2")] + public Task UploadAttachment2( + [FromForm][Required] CaseAttachmentModel2 model, + [Required] IFormFile contents) + { + return Task.FromResult(Ok()); + } + + public class CaseAttachmentModel2 + { + [Required] + public string Title { get; init; } + + public int? MessageId { get; set; } + } } } \ No newline at end of file diff --git a/src/NSwag.Generation.AspNetCore.Tests/Parameters/FormDataTests.cs b/src/NSwag.Generation.AspNetCore.Tests/Parameters/FormDataTests.cs index ee27012b5..2166874f7 100644 --- a/src/NSwag.Generation.AspNetCore.Tests/Parameters/FormDataTests.cs +++ b/src/NSwag.Generation.AspNetCore.Tests/Parameters/FormDataTests.cs @@ -102,5 +102,56 @@ public async Task WhenOperationHasFormDataComplex_ThenItIsInRequestBody() } },".Replace("\r", ""), json.Replace("\r", "")); } + + [Fact] + public async Task WhenOperationHasFormDataComplexWithRequiredProperties_ThenItIsInRequestBody() + { + // Arrange + var settings = new AspNetCoreOpenApiDocumentGeneratorSettings + { + SchemaSettings = new NewtonsoftJsonSchemaGeneratorSettings + { + SchemaType = SchemaType.OpenApi3 + } + }; + + // Act + var document = await GenerateDocumentAsync(settings, typeof(FileUploadController)); + var json = document.ToJson(); + + // Assert + var operation = document.Operations.First(o => o.Operation.OperationId == "FileUpload_UploadAttachment2").Operation; + + Assert.NotNull(operation); + Assert.Contains(@"""requestBody"": { + ""content"": { + ""multipart/form-data"": { + ""schema"": { + ""type"": ""object"", + ""required"": [ + ""Title"", + ""contents"" + ], + ""properties"": { + ""Title"": { + ""type"": ""string"", + ""nullable"": false + }, + ""MessageId"": { + ""type"": ""integer"", + ""format"": ""int32"", + ""nullable"": true + }, + ""contents"": { + ""type"": ""string"", + ""format"": ""binary"", + ""nullable"": false + } + } + } + } + } + },".Replace("\r", ""), json.Replace("\r", "")); + } } } \ No newline at end of file diff --git a/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs b/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs index 7cf08d180..53dbd391f 100644 --- a/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs +++ b/src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs @@ -348,10 +348,18 @@ private JsonSchema CreateOrGetFormDataSchema(OperationProcessorContext context) return requestBody.Content[MultipartFormData].Schema; } - private static JsonSchemaProperty CreateFormDataProperty(OperationProcessorContext context, ExtendedApiParameterDescription extendedApiParameter, JsonSchema schema) + private JsonSchemaProperty CreateFormDataProperty(OperationProcessorContext context, ExtendedApiParameterDescription extendedApiParameter, JsonSchema schema) { - return context.SchemaGenerator.GenerateWithReferenceAndNullability( + var formDataProperty = context.SchemaGenerator.GenerateWithReferenceAndNullability( extendedApiParameter.ApiParameter.Type.ToContextualType(extendedApiParameter.Attributes), context.SchemaResolver); + + var contextualPropertyType = extendedApiParameter.ParameterType.ToContextualType(); + var typeDescription = _settings.SchemaSettings.ReflectionService.GetDescription(contextualPropertyType, _settings.SchemaSettings); + var isRequired = extendedApiParameter.IsRequired(_settings.RequireParametersWithoutDefault); + formDataProperty.IsRequired = isRequired; + formDataProperty.IsNullableRaw = _settings.AllowNullableBodyParameters && !isRequired && typeDescription.IsNullable; + + return formDataProperty; } private bool IsFileArray(Type type, JsonTypeDescription typeInfo) @@ -527,7 +535,7 @@ public bool IsRequired(bool requireParametersWithoutDefault) // available in asp.net core >= 2.2 if (ApiParameter.HasProperty("IsRequired")) { - isRequired = ApiParameter.TryGetPropertyValue("IsRequired", false); + isRequired = ApiParameter.TryGetPropertyValue("IsRequired", false) || ApiParameter.ModelMetadata?.IsRequired == true; } else { @@ -538,7 +546,6 @@ public bool IsRequired(bool requireParametersWithoutDefault) } else if (ApiParameter.ModelMetadata != null && ApiParameter.ModelMetadata.IsBindingRequired) - { isRequired = true; }