diff --git a/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs b/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs index cb017ae61..832480c15 100644 --- a/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs +++ b/src/NSwag.Generation.AspNetCore/AspNetCoreOpenApiDocumentGenerator.cs @@ -157,9 +157,9 @@ public static JsonSerializerOptions GetSystemTextJsonSettings(IServiceProvider s var options = serviceProvider.GetService(optionsType); var value = optionsType.GetProperty("Value")?.GetValue(options); var jsonOptions = value?.GetType().GetProperty("JsonSerializerOptions")?.GetValue(value); - if (jsonOptions is JsonSerializerOptions) + if (jsonOptions is JsonSerializerOptions serializerOptions) { - return (JsonSerializerOptions)jsonOptions; + return serializerOptions; } } catch @@ -336,7 +336,12 @@ private List> Add if (pathItem.ContainsKey(operation.Method)) { - throw new InvalidOperationException($"The method '{operation.Method}' on path '{path}' is registered multiple times."); + var conflictingApiDescriptions = operations + .Where(t => t.Item1.Path == operation.Path && t.Item1.Method == operation.Method) + .Select(t => t.Item2) + .ToList(); + + throw new InvalidOperationException($"The method '{operation.Method}' on path '{path}' is registered multiple times for actions {string.Join(", ", conflictingApiDescriptions.Select(apiDesc => apiDesc.ActionDescriptor.DisplayName))}."); } pathItem[operation.Method] = operation.Operation; diff --git a/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs b/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs index 419b3b64f..9eeae0a00 100644 --- a/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs +++ b/src/NSwag.Generation.WebApi/Processors/OperationParameterProcessor.cs @@ -269,7 +269,7 @@ private static void EnsureSingleBodyParameter(OpenApiOperationDescription operat { 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."); + throw new InvalidOperationException($"The operation '{operationDescription.Operation.OperationId}' has more than one body parameter."); } } diff --git a/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs b/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs index c62b03446..99eacf606 100644 --- a/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs +++ b/src/NSwag.Generation.WebApi/WebApiOpenApiDocumentGenerator.cs @@ -214,19 +214,24 @@ private bool AddOperationDescriptionsToDocument(OpenApiDocument document, Type c { var path = operation.Path.Replace("//", "/"); - if (!document.Paths.TryGetValue(path, out OpenApiPathItem value)) + if (!document.Paths.TryGetValue(path, out var pathItem)) { - value = []; - document.Paths[path] = value; + pathItem = []; + document.Paths[path] = pathItem; } - if (value.ContainsKey(operation.Method)) + if (pathItem.ContainsKey(operation.Method)) { - throw new InvalidOperationException("The method '" + operation.Method + "' on path '" + path + "' is registered multiple times " + + var conflictingOperationDisplayNames = operations + .Where(t => t.Item1.Path == operation.Path && t.Item1.Method == operation.Method) + .Select(t => GetDisplayName(controllerType, t.Item2)) + .ToList(); + + throw new InvalidOperationException($"The method '{operation.Method}' on path '{path}' is registered multiple times for action {string.Join(", ", conflictingOperationDisplayNames)} " + "(check the DefaultUrlTemplate setting [default for Web API: 'api/{controller}/{id}'; for MVC projects: '{controller}/{action}/{id?}'])."); } - value[operation.Method] = operation.Operation; + pathItem[operation.Method] = operation.Operation; addedOperations++; } } @@ -234,6 +239,11 @@ private bool AddOperationDescriptionsToDocument(OpenApiDocument document, Type c return addedOperations > 0; } + private static string GetDisplayName(Type controllerType, MethodInfo method) + { + return $"{controllerType.FullName}.{method.Name} ({controllerType.Assembly.GetName().Name})"; + } + private bool RunOperationProcessors(OpenApiDocument document, Type controllerType, MethodInfo methodInfo, OpenApiOperationDescription operationDescription, List allOperations, OpenApiDocumentGenerator generator, OpenApiSchemaResolver schemaResolver) { @@ -326,11 +336,11 @@ private string GetOperationId(OpenApiDocument document, string controllerName, M controllerName = controllerName.Substring(0, controllerName.Length - 10); } - operationId = controllerName + "_" + GetActionName(method); + operationId = $"{controllerName}_{GetActionName(method)}"; } var number = 1; - while (document.Operations.Any(o => o.Operation.OperationId == operationId + (number > 1 ? "_" + number : string.Empty))) + while (document.Operations.Any(o => o.Operation.OperationId == operationId + (number > 1 ? $"_{number}" : string.Empty))) { number++; } @@ -359,7 +369,7 @@ private List GetHttpPaths(Type controllerType, MethodInfo method) } else if (routePrefixAttribute != null) { - httpPaths.Add(routePrefixAttribute.Prefix + "/" + attribute.Template); + httpPaths.Add($"{routePrefixAttribute.Prefix}/{attribute.Template}"); } else if (routeAttributesOnClass != null) { @@ -371,7 +381,7 @@ private List GetHttpPaths(Type controllerType, MethodInfo method) { foreach (var routeAttributeOnClass in routeAttributesOnClass) { - httpPaths.Add(routeAttributeOnClass.Template + "/" + attribute.Template); + httpPaths.Add($"{routeAttributeOnClass.Template}/{attribute.Template}"); } } } @@ -385,7 +395,7 @@ private List GetHttpPaths(Type controllerType, MethodInfo method) { foreach (var routeAttributeOnClass in routeAttributesOnClass) { - httpPaths.Add(routePrefixAttribute.Prefix + "/" + routeAttributeOnClass.Template); + httpPaths.Add($"{routePrefixAttribute.Prefix}/{routeAttributeOnClass.Template}"); } } else if (routePrefixAttribute != null) @@ -422,7 +432,7 @@ private List GetHttpPaths(Type controllerType, MethodInfo method) private static IEnumerable ExpandOptionalHttpPathParameters(string path, MethodInfo method) { var segments = path.Split(['/'], StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; i < segments.Length; i++) + for (var i = 0; i < segments.Length; i++) { var segment = segments[i]; if (segment.EndsWith("?}", StringComparison.Ordinal)) diff --git a/src/NSwag.sln b/src/NSwag.sln index 254f7791f..ea5a902b5 100644 --- a/src/NSwag.sln +++ b/src/NSwag.sln @@ -475,30 +475,6 @@ Global {AC3D8125-AE21-49FC-A217-D96C7B585FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {AC3D8125-AE21-49FC-A217-D96C7B585FF9}.Release|x64.ActiveCfg = Release|Any CPU {AC3D8125-AE21-49FC-A217-D96C7B585FF9}.Release|x86.ActiveCfg = Release|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x64.ActiveCfg = Debug|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x64.Build.0 = Debug|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x86.ActiveCfg = Debug|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x86.Build.0 = Debug|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|Any CPU.Build.0 = Release|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x64.ActiveCfg = Release|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x64.Build.0 = Release|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x86.ActiveCfg = Release|Any CPU - {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x86.Build.0 = Release|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x64.ActiveCfg = Debug|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x64.Build.0 = Debug|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x86.ActiveCfg = Debug|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x86.Build.0 = Debug|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|Any CPU.Build.0 = Release|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x64.ActiveCfg = Release|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x64.Build.0 = Release|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x86.ActiveCfg = Release|Any CPU - {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x86.Build.0 = Release|Any CPU {CF6112E5-20FD-4B22-A6C0-20AF6B3396F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CF6112E5-20FD-4B22-A6C0-20AF6B3396F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {CF6112E5-20FD-4B22-A6C0-20AF6B3396F3}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -535,6 +511,30 @@ Global {F0569608-BD55-4316-94F0-E85A14D7FE14}.Release|x64.Build.0 = Release|Any CPU {F0569608-BD55-4316-94F0-E85A14D7FE14}.Release|x86.ActiveCfg = Release|Any CPU {F0569608-BD55-4316-94F0-E85A14D7FE14}.Release|x86.Build.0 = Release|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x64.Build.0 = Debug|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Debug|x86.Build.0 = Debug|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|Any CPU.Build.0 = Release|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x64.ActiveCfg = Release|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x64.Build.0 = Release|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x86.ActiveCfg = Release|Any CPU + {DE82965A-6935-43E0-A9A1-F3F35B4487EB}.Release|x86.Build.0 = Release|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x64.ActiveCfg = Debug|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x64.Build.0 = Debug|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x86.ActiveCfg = Debug|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Debug|x86.Build.0 = Debug|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|Any CPU.Build.0 = Release|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x64.ActiveCfg = Release|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x64.Build.0 = Release|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x86.ActiveCfg = Release|Any CPU + {24693FBC-445E-4360-A1E8-B6F136C563FB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -573,11 +573,11 @@ Global {B6467D5A-7B13-4B06-89BE-2BC7B6615956} = {1030C3AA-7549-473B-AA7E-5DFAC2F9D37A} {F237A592-2D74-4C38-B222-2C91029B87F8} = {48E8B044-3374-4F39-A180-9E01F7A10785} {AC3D8125-AE21-49FC-A217-D96C7B585FF9} = {6F5E4FDF-0A82-42D5-94AC-A9CD43CC931D} - {DE82965A-6935-43E0-A9A1-F3F35B4487EB} = {D8CC0D1C-8DAC-49FE-AA78-C028DC124DD5} - {24693FBC-445E-4360-A1E8-B6F136C563FB} = {D8CC0D1C-8DAC-49FE-AA78-C028DC124DD5} {CF6112E5-20FD-4B22-A6C0-20AF6B3396F3} = {F0F26A35-C4B6-42D0-A1DF-98CA46A5C560} {2A166077-2189-4376-A38B-8E362A319028} = {D8CC0D1C-8DAC-49FE-AA78-C028DC124DD5} {F0569608-BD55-4316-94F0-E85A14D7FE14} = {D8CC0D1C-8DAC-49FE-AA78-C028DC124DD5} + {DE82965A-6935-43E0-A9A1-F3F35B4487EB} = {D8CC0D1C-8DAC-49FE-AA78-C028DC124DD5} + {24693FBC-445E-4360-A1E8-B6F136C563FB} = {D8CC0D1C-8DAC-49FE-AA78-C028DC124DD5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {98FCEEE2-A45C-41E7-B2ED-1B14755E9067}