Actions require unique method/path combination for Swagger

28,159

Solution 1

You can resolve it as follows:

services.AddSwaggerGen (c =>
  {
    other configs;
    c.ResolveConflictingActions (apiDescriptions => apiDescriptions.First ());
  });
//in the Startup.cs class in the ConfigureServices method

or you can put routes to differentiate your methods, for example:

[HttpGet("~/getsomething")]
[HttpGet("~/getothersomething")]

Solution 2

I changed the controller route to following:

[Route("api/[controller]/[action]")]

or you can also define explicit route for action as well:

[Route("GetById")]

Solution 3

you need to map id into HttpGet.

[HttpGet("{id}")]
public async Task<DataStoreQuery> GetByIdAsync(int id)
{
    try
    {
        return await _dataStoreService.GetByIdAsync(id);
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
        throw;
    }
}

when you specify HttpGet by not providing template, Swashbuckle tries to use default map for both of them. hence conflict occurs.

Solution 4

You can also merge methods with same endpoints to one with optional parameters. Example of implementation tested in net core 5 project:

services.AddSwaggerGen(c => 
{
    c.ResolveConflictingActions(apiDescriptions =>
    {
        var descriptions = apiDescriptions as ApiDescription[] ?? apiDescriptions.ToArray();
        var first = descriptions.First(); // build relative to the 1st method
        var parameters = descriptions.SelectMany(d => d.ParameterDescriptions).ToList();

        first.ParameterDescriptions.Clear();
        // add parameters and make them optional
        foreach (var parameter in parameters)
            if (first.ParameterDescriptions.All(x => x.Name != parameter.Name))
            {
                first.ParameterDescriptions.Add(new ApiParameterDescription
                {
                    ModelMetadata = parameter.ModelMetadata,
                    Name = parameter.Name,
                    ParameterDescriptor = parameter.ParameterDescriptor,
                    Source = parameter.Source,
                    IsRequired = false,
                    DefaultValue = null
                });
            }
        return first;
    });
});
Share:
28,159

Related videos on Youtube

ekad
Author by

ekad

Updated on July 09, 2022

Comments

  • ekad
    ekad almost 2 years

    I have 2 HTTP GET method in same controller and give me this error

    HTTP method "GET" & path "api/DataStore" overloaded by actions - DPK.HostApi.Controllers.DataStoreController.GetByIdAsync (DPK.HostApi),DPK.HostApi.Controllers.DataStoreController.GetAllAsync (DPK.HostApi). Actions require unique method/path combination for Swagger 2.0.

    My Controller :

    [Route("api/[controller]")]
    [ApiController]
    public class DataStoreController : ApiControllerBase
    {
        private readonly IDataStoreService _dataStoreService;
    
        public DataStoreController(IDataStoreService dataStoreService)
        {
            _dataStoreService = dataStoreService;
        }
    
    
        [HttpPost]
        public async Task<IActionResult> PostAsync([FromBody] DataStoreCommand dataStoreCommand)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    await _dataStoreService.PostAsync(dataStoreCommand);
                    return Ok();
                }
    
                var errorList = ModelState.Values.SelectMany(m => m.Errors).Select(e => e.ErrorMessage).ToList();
                return ValidationProblem();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
    
        [HttpPut]
        public async Task<IActionResult> PutAsync([FromBody] DataStoreCommand dataStoreCommand)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    await _dataStoreService.PutAsync(dataStoreCommand);
                    return Ok();
                }
    
                var errorList = ModelState.Values.SelectMany(m => m.Errors).Select(e => e.ErrorMessage).ToList();
                return ValidationProblem();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
    
        [HttpDelete]
        public async Task<IActionResult> DeleteAsync(int id)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    var item = await _dataStoreService.GetByIdAsync(id);
                    await _dataStoreService.DeleteAsync(item);
                    return Ok();
                }
    
                var errorList = ModelState.Values.SelectMany(m => m.Errors).Select(e => e.ErrorMessage).ToList();
                return ValidationProblem();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
    
        [HttpGet]
        public async Task<DataStoreQuery> GetByIdAsync(int id)
        {
            try
            {
                return await _dataStoreService.GetByIdAsync(id);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
    
    
    
    
        [HttpGet]
        public async Task<IEnumerable<DataStoreQuery>> GetAllAsync(string instanceName, string dbname, string userName, string userPass, bool isActive, DateTime? startCreatedDate, DateTime? endCreatedDate, DateTime? startModifiedDate, DateTime? endModifiedDate)
        {
            object[] parameters = { instanceName, dbname, userName, userPass, isActive, startCreatedDate, endCreatedDate, startModifiedDate,  endModifiedDate};
            var parameterName = "@instanceName , @dbname , @userName , @userPass , @isActive , @startCreatedDate , @endCreatedDate , @startModifiedDate , @endModifiedDate";
            try
            {
                return await _dataStoreService.ExecWithStoreProcedure(parameterName, parameters);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
    
    
    
    }
    

    My Startup :

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }
    
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info
                {
                    Version = "v1",
                    Title = " ",
                    Description = " ",
                    TermsOfService = "None",
                    Contact = new Contact() { Name = " ", Email = " ", Url = " " }
                });
            });
        }
    
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            app.UseMvc();
    
    
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            });
        }
    }
    
  • Neurion
    Neurion about 4 years
    Santos The first solution would mean only the first action gets documented in Swagger. Generally you want to avoid undocumented endpoints...
  • user1574598
    user1574598 almost 3 years
    For some reason I don't get api/v1/[controller]/myroute in the docs when I use [Route], I just get /myroute whilst all the other HTTP verbs have the full api path. First solution is reasonable though as it seems to pull in the method names as the end points.