Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 501
antoninkriz Avatar asked Aug 11 '19 23:08

antoninkriz


2 Answers

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.

like image 140
antoninkriz Avatar answered Oct 04 '22 22:10

antoninkriz


Had the same error but without any third party library involved.

Problem in my code was, that there where public methods in my controller base class being used by the controller but not to be exposed by the controller.

Example:

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class TestController : GenericController<TestClass>
{
    public TestController() : base()
    {
    }
}
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class GenericController<T1>
{
    public GenericController()
    {
    // ...
    }
    
    [HttpGet]
    public async Task<ActionResult<IEnumerable<T1>>> Get()
    {
    // ...
    }
    
    public void DoStuff()
    {
    // ...
    }
}

Changing the access modifier of DoStuff to internal solved the problem.

like image 21
sc911 Avatar answered Oct 04 '22 22:10

sc911