Search code examples
c#asp.netasp.net-coreasp.net-web-apiswagger

ASP.Net Core + Swagger - Actions require an explicit HttpMethod binding for Swagger 2.0


I have a project MyProject.Api with following structure:

Controllers/
- Api/
  - UsersController.cs
- HomeController.cs
Startup.cs

where HomeController.cs looks like this:

namespace MyProject.Api.Controllers
{
    public class HomeController : Controller
    {
        private readonly IHostingEnvironment _hostingEnvironment;

        public HomeController(IHostingEnvironment hostingEnv) {...}

        [HttpGet]
        public async Task<IActionResult> Index() {...}

        [HttpGet("sitemap.xml")]
        public IActionResult SiteMap() {...}

        [HttpGet("error")]
        public IActionResult Error() {...}
    }
}

and UsersController.cs looks like this:

namespace MyProject.Api.Controllers.Api
{
    [Route("api/[controller]")]
    public class UsersController : Controller
    {
        private readonly ApiHelper<UsersController> _apiHelper;
        private readonly IUserService _userService;

        public UsersController(ILogger<UsersController> logger, IUserService userService) {...}

        [HttpPost("login")]
        public async Task<JsonResult> Login([FromBody] LoginRequest request) {...}

        [HttpPost("register")]
        public async Task<JsonResult> Register([FromBody] RegisterRequest request) {...}

        [HttpGet("renew")]
        [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
        public async Task<JsonResult> Renew() {...}
    }
}

and Startup.cs:


namespace MyProjet.Api
{
    public class Startup
    {
        private IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration) {...}

        public void ConfigureServices(IServiceCollection services)
        {
            ...

            services.AddSwaggerGen(c => c.SwaggerDoc("v1", new Info {Title = "Web Api Docs", Version = "v1"}));
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            ...

            app.UseSwagger();
            app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
            app.MapWhen(x => !x.Request.Path.Value.StartsWith("/swagger", StringComparison.OrdinalIgnoreCase), builder =>
            {
                builder.UseMvc(routes =>
                {
                    routes.MapSpaFallbackRoute(
                       "spa-fallback",
                        new {controller = "Home", action = "Index"});
                });
            });
        }
    }
}

when I load /swagger the UI loads successfully, but with following error:

Fetch error
Internal Server Error /swagger/v1/swagger.json

and with this error on the server side

System.NotSupportedException: Ambiguous HTTP method for action - WebEssentials.AspNetCore.Pwa.PwaController.ServiceWorkerAsync (WebEssentials.AspNetCore.Pwa). Actions require an explicit HttpMethod binding for Swagger 2.0
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreatePathItem(IEnumerable`1 apiDescriptions, ISchemaRegistry schemaRegistry)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath, String[] schemes)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.SpaServices.Webpack.ConditionalProxyMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.SpaServices.Webpack.ConditionalProxyMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

but all methods have unique route, unique name and their HTTP methods are explicitly bound. I've tried adding [Route("")] to the HomeController.cs, but that did not work either.

What am I doing wrong?


Solution

  • As @devNull said, the mistake is not in my code but in WebEssentials.AspNetCore.Pwa.PwaController.ServiceWorkerAsync.

    UPDATE:

    My pull request with fix (Added explicit HttpMethod bindings) was now merged to WebEssentials.AspNetCore.ServiceWorker repository and will hopefully be available in next release on NuGet with version newer than 1.0.59

    OLD SOLUTION:

    I found the following solution posted here by Gandii.

    1. Create somewhere class ApiExplorerIgnores with following content
    public class ApiExplorerIgnores : IActionModelConvention
    {
        public void Apply(ActionModel action)
        {
            if (action.Controller.ControllerName.Equals("Pwa"))
                action.ApiExplorer.IsVisible = false;
        }
    }
    
    1. Add following code to method ConfigureServices in Startup.cs
    services.AddMvc(c => c.Conventions.Add(new ApiExplorerIgnores()))
    

    This should hide the PwaController from ApiExplorer which is used by Swashbuckle.