domaindrivendev / Swashbuckle.AspNetCore

Swagger tools for documenting API's built on ASP.NET Core
MIT License
5.23k stars 1.31k forks source link

6.3.0 System.ArgumentException: Literal sections cannot contain the '?' character. #2385

Open spaasis opened 2 years ago

spaasis commented 2 years ago

Hi,

Recently updated from 6.2.1 to 6.3.0 and stuff started breaking. I'm running an OData controller via aspnet-api-versioning

Any endpoint within an OData controller with manually defined query parameters results in System.ArgumentException: The literal section 'QuantityUnits?onlyValid=' is invalid. Literal sections cannot contain the '?' character. (Parameter 'routeTemplate')

Controller and endpoint:

namespace WebApi.V1.CodeLists;

[ODataRoutePrefix("QuantityUnits")]
public class QuantityUnitsController : BaseODataController {
    [EnableQuery]
    [ProducesResponseType(typeof(ODataListWrapper<IQueryable<QuantityUnitOutput>>), Status200OK)]
    public async Task<IQueryable<QuantityUnitOutput>> Get(bool onlyValid = true) => ...
}

This works in 6.2.1. In 6.2.3 I observed that the swagger.json generation works, but the endpoint produced has a trailing {, e.g. /api/v1/odata/QuantityUnits{ which of course doesn't really work.

Any idea where to start digging?

Full stack trace:

[14:07:26 INF] Request starting HTTP/2 GET https://localhost:44395/swagger/v1/swagger.json - -
[14:07:26 ERR] An unhandled exception has occurred while executing the request.
System.ArgumentException: The literal section 'QuantityUnits?onlyValid=' is invalid. Literal sections cannot contain the '?' character. (Parameter 'routeTemplate')
 ---> Microsoft.AspNetCore.Routing.Patterns.RoutePatternException: The literal section 'QuantityUnits?onlyValid=' is invalid. Literal sections cannot contain the '?' character.
   at Microsoft.AspNetCore.Routing.Patterns.RoutePatternParser.Parse(String pattern)
   at Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory.Parse(String pattern)
   at Microsoft.AspNetCore.Routing.Template.TemplateParser.Parse(String routeTemplate)
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Routing.Template.TemplateParser.Parse(String routeTemplate)
   at Swashbuckle.AspNetCore.SwaggerGen.ApiDescriptionExtensions.RelativePathSansParameterConstraints(ApiDescription apiDescription)
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.<>c.<GeneratePaths>b__6_0(ApiDescription apiDesc)
   at System.Linq.Lookup`2.Create(IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
   at System.Linq.GroupedEnumerable`2.GetEnumerator()
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Hellang.Middleware.ProblemDetails.ProblemDetailsMiddleware.Invoke(HttpContext context)
ruapho commented 2 years ago

Same here. Works in Version 6.2.3 and breaks in 6.3.0.

Sergey-Terekhin commented 2 years ago

Same story

spaasis commented 2 years ago

Still happens in 6.3.1

spaasis commented 2 years ago

Some more diagnostics and a workaround:

Besides the explicit config in the opening post, the same issue is caused when an OData controller has a PATCH/POST/PUT that takes the parameters from query instead of request body:

    [HttpPatch]
    public async Task<ActionResult> Patch([FromQuery] MyCommand cmd) {

This causes the relative url to have ? and the error is thrown.

I worked around this myself by splitting these write operations to a different, non-odata controller

spaasis commented 2 years ago

Still broken in 6.4.0

seriouz commented 2 years ago

Still broken! Cannot define a route with [Route("api/test?id={var}")]

icnocop commented 2 years ago

Work-around:

  1. Download build artifacts from https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2473: https://ci.appveyor.com/project/domaindrivendev/ahoy/builds/44347663/artifacts

Swashbuckle.AspNetCore.6.4.0-preview-2078.zip

  1. Create/update NuGet.Config and point to local repository:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
    <packageSources>
      <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
      <add key="Local Swashbuckle.AspNetCore" value="..\..\..\Third Party\Swashbuckle\Swashbuckle.AspNetCore\6.4.0-preview-2078" />
    </packageSources>
    </configuration>
  2. Update NuGet package references to use these packages instead.

icnocop commented 1 year ago

As another work-around, migrate to NSwag.AspNetCore and use the following document processor:

    using System.Collections.Generic;
    using System.Linq;
    using NJsonSchema;
    using NSwag;
    using NSwag.Generation.Processors;
    using NSwag.Generation.Processors.Contexts;

    /// <summary>
    /// Removes query string parameters from paths in the Open API specification document.
    /// </summary>
    /// <seealso cref="NSwag.Generation.Processors.IDocumentProcessor" />
    public class RemoveQueryStringParametersFromPathsDocumentProcessor : IDocumentProcessor
    {
        /// <inheritdoc/>
        public void Process(DocumentProcessorContext context)
        {
            List<string> pathsToFix = new List<string>();

            foreach (var path in context.Document.Paths)
            {
                if (path.Key.Contains('?'))
                {
                    pathsToFix.Add(path.Key);
                }
            }

            foreach (var pathToFix in pathsToFix)
            {
                string key = pathToFix[..pathToFix.IndexOf('?')];
                var pathItem = context.Document.Paths[pathToFix];
                if (context.Document.Paths.ContainsKey(key))
                {
                    context.Document.Paths[key].AddRange(pathItem);
                }
                else
                {
                    context.Document.Paths.Add(key, pathItem);
                }

                context.Document.Paths.Remove(pathToFix);
            }
        }
    }

Usage, in Startup.cs in ConfigureServices:

services.AddOpenApiDocument((settings, serviceProvider) =>
{
    settings.DocumentProcessors.Add(new RemoveQueryStringParametersFromPathsDocumentProcessor());
}
JLeczycki commented 1 year ago

It still exists in 6.5.0... Any solution for that for shawbuckle package?

github-actions[bot] commented 5 months ago

This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.

martincostello commented 5 months ago

Community contributions to fix this are welcome.

Otherwise this will continue to be an issue as long as this issue is open.