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 WebApi (non-ASP.NET-Core) operation generator throws when an operation's actual parameters contain more than one parameter with OpenApiParameterKind.Body after processing. An OpenAPI operation permits only a single body parameter, so EnsureSingleBodyParameter (called from Process) enforces this and throws InvalidOperationException with the operation's OperationId.

Solutions

  1. Merge the multiple body inputs into one request DTO and use it as the single body parameter.
  2. Decorate the non-body parameter with [FromUri] (Web API 2) so it is treated as route/query instead of body.
  3. Mark exactly one parameter [FromBody] and give the others explicit non-body binding sources.
  4. If a custom NSwag processor adds the extra body parameter, correct it to emit at most one.

Example fix

// before
public void Post(OrderDto order, AuditContext audit) // both become body

// after
public void Post([FromBody] OrderDto order, [FromUri] AuditContext audit)
Defensive patterns

Strategy: validation

Validate before calling

// Web API 2: exactly one implicit/explicit body parameter per action
var bodyParams = actionDescriptor.GetParameters()
    .Where(p => !p.GetCustomAttributes(typeof(FromUriAttribute), false).Any());
bool tooManyBodies = bodyParams.Count() > 1;

Try / catch

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

Prevention

When it happens

Trigger: Generating a document for a Web API 2 / OWIN controller action with multiple parameters that resolve to body kind (e.g. two complex typed parameters without [FromBody]/[FromUri] markers, so both default to body), or two parameters explicitly marked [FromBody].

Common situations: Classic ASP.NET Web API actions taking two complex types where one was intended as [FromUri]; migrating actions between frameworks and keeping duplicate body bindings; custom parameter processors adding a second body parameter.

Related errors


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

Appendix: source

Thrown at src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs:272

        /// </summary>
        /// <param name="operationDescription">Operation to check.</param>
        /// <param name="schemaType">Schema type.</param>
        private static void UpdateNullableRawOperationParameters(OpenApiOperationDescription operationDescription, SchemaType schemaType)
        {
            if (schemaType == SchemaType.OpenApi3)
            {
                foreach (OpenApiParameter openApiParameter in operationDescription.Operation.Parameters)
                {
                    openApiParameter.IsNullableRaw = null;
                }
            }
        }

        private static void EnsureSingleBodyParameter(OpenApiOperationDescription operationDescription)
        {
            if (operationDescription.Operation.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)
        {
            if (operationDescription.Operation.ActualParameters.Any(p => p.IsBinary || p.ActualSchema.IsBinary))
            {
                operationDescription.Operation.TryAddConsumes("multipart/form-data");
            }
        }

        private static void RemoveUnusedPathParameters(OpenApiOperationDescription operationDescription, string httpPath)
        {
            operationDescription.Path = Regex.Replace(httpPath, "{(.*?)(:(([^/]*)?))?}", match =>
            {
                var parameterName = match.Groups[1].Value.TrimEnd('?');
                if (operationDescription.Operation.ActualParameters.Any(p => p.Kind == OpenApiParameterKind.Path && string.Equals(p.Name, parameterName, StringComparison.OrdinalIgnoreCase)))
                {

View on GitHub (pinned to 63daf8fcc3)