RicoSuter/NSwag · error · InvalidOperationException

The operation ' ' has more than one body parameter.

Error message

The operation '{operationDescription.Operation.OperationId}' has more than one body parameter.

What it means

The ASP.NET Core operation generator throws when, after parameter processing, an operation ends up with more than one parameter of OpenApiParameterKind.Body. OpenAPI allows at most one requestBody/body parameter per operation, so NSwag validates this invariant in EnsureSingleBodyParameter (called from Process) and throws InvalidOperationException.

Solutions

  1. Merge the multiple body parameters into a single request DTO class and bind that one [FromBody] model.
  2. Change one parameter's binding source, e.g. move auxiliary data to [FromQuery], [FromRoute], or [FromHeader].
  3. If extra body params come from a custom IOperationProcessor or parameter filter you added, fix it so only one body parameter is emitted.
  4. For raw body + metadata, accept the raw body and pass metadata via route/query instead.

Example fix

// before
public IActionResult Save([FromBody] OrderDto order, [FromBody] SaveOptions options)

// after
public class SaveRequest { public OrderDto Order { get; set; } public SaveOptions Options { get; set; } }
public IActionResult Save([FromBody] SaveRequest request)
Defensive patterns

Strategy: validation

Validate before calling

// Guard action signatures: at most one [FromBody] parameter per action
bool hasMultipleBodyParams = methodInfo.GetParameters()
    .Count(p => p.GetCustomAttributes(typeof(FromBodyAttribute), false).Any()) > 1;

Try / catch

try { document = generator.Generate(settings); }
catch (InvalidOperationException ex) when (ex.Message.Contains("more than one body parameter"))
{
    logger.LogError(ex, "Operation has multiple body parameters");
}

Prevention

When it happens

Trigger: Generating a document for an action whose signature contains two [FromBody] parameters (e.g. [FromBody] OrderDto order, [FromBody] OptionsDto options), or a custom parameter processor/binding that produces additional body-kind parameters for the same operation.

Common situations: Migrating an action that accepted a raw body plus a JSON payload; combining [FromBody] with a custom model binder registered as body; copy-pasting an action and forgetting to change a parameter's source attribute; third-party attributes injecting extra body parameters.

Related errors


AI-assisted analysis of RicoSuter/NSwag@63daf8fcc3 (2026-09-14). Data as JSON: /api/errors/3e6f1ce923527efc. Report an issue: GitHub.

Appendix: source

Thrown at src/NSwag.Generation.AspNetCore/Processors/OperationParameterProcessor.cs:255

                foreach (var mimeType in mimeTypes)
                {
                    operationDescription.Operation.RequestBody.Content[mimeType] = new OpenApiMediaType
                    {
                        Schema = mimeType == "application/json" ? JsonSchema.CreateAnySchema() : new JsonSchema
                        {
                            Type = _settings.SchemaSettings.SchemaType == SchemaType.Swagger2 ? JsonObjectType.File : JsonObjectType.String,
                            Format = _settings.SchemaSettings.SchemaType == SchemaType.Swagger2 ? null : JsonFormatStrings.Binary,
                        }
                    };
                }
            }
        }

        private static void EnsureSingleBodyParameter(OpenApiOperationDescription operationDescription, List<OpenApiParameter> actualParameters)
        {
            if (actualParameters.Count(p => p.Kind == OpenApiParameterKind.Body) > 1)
            {
                throw new InvalidOperationException($"The operation '{operationDescription.Operation.OperationId}' has more than one body parameter.");
            }
        }

        private static void UpdateConsumedTypes(OpenApiOperationDescription operationDescription, List<OpenApiParameter> actualParameters)
        {
            if (actualParameters.Any(static p => p.IsBinary || p.ActualSchema.IsBinary))
            {
                operationDescription.Operation.TryAddConsumes("multipart/form-data");
            }
        }

        private static void RemoveUnusedPathParameters(OpenApiOperationDescription operationDescription, List<OpenApiParameter> actualParameters, string httpPath)
        {
            operationDescription.Path = "/" + unusedPathParametersRegex.Replace(httpPath, match =>
            {
                var parameterName = match.Groups[1].Value.TrimEnd('?');
                if (actualParameters.Any(p => p.Kind == OpenApiParameterKind.Path && string.Equals(p.Name, parameterName, StringComparison.OrdinalIgnoreCase)))
                {

View on GitHub (pinned to 63daf8fcc3)