Search code examples
rest.net-corerouteshttp-getwebapi

HttpGetAttribute doesn't work in core web api


Well known situation. I need two endpoints

GetAll -> api/brands

GetById -> api/brands/1

[ApiController]
[Route("api/[controller]")]
public class BrandsController : ControllerBase
{
    private readonly BrandRepository repository;

    public BrandsController(BrandRepository repository)
    {
        this.repository = repository;
    }

    [HttpGet("{id:int}")]
    public async Task<ActionResult> GetById(int id)
    {
        var brand = await repository.FindAsync(id);
        if (brand == null)
        {
            return NotFound();
        }

        return Ok(brand);
    }

    [HttpGet("")]
    public ActionResult<IEnumerable<Brand>> GetAll()
    {
        var brands = repository.GetAll().ToList(); 

        return Ok(brands);
    }}

So, I always get into GetAll() Any ideas? Help, please :)

Postman request

Is it a correct namespace?

using Microsoft.AspNetCore.Mvc;

for

[HttpGet]

Startup.cs

namespace BackOffice
{
    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.AddControllers();

            services.AddDbContext<ApplicationDbContext>(
                options => 
                options.UseMySql(Configuration.GetConnectionString("local")));

            services.AddTransient<BrandRepository, BrandRepository>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(
                endpoints =>
                {
                    endpoints.MapControllers();
                });

            app.UseCors();
        }
    }
}

dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd


Solution

  • Change your the attribute on your GetAll action to simply [HttpGet] and then change the attribute on your GetById action to [HttpGet("{id}")] .

    You can use a constraint to id if need be but in your case I don't see any need for it. Generally you can use constraints when you have multiple actions on the same route but with different parameter types. For example, "api/brands/1" to get by integer ID and then maybe you have another action that is mapped to "api/brands/gucci" that will search for the brand by string name. Then you can use the {id:int} and {id:string} constraints in your route template to define which action to invoke.

    Also make sure you use IActionResult when declaring the action return types. You don't want to use the concrete ActionResult type. Code samples below.

    For the GetById action :

    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(int id)
    {
        var brand = await repository.FindAsync(id);
        if (brand == null)
        {
            return NotFound();
        }
    
        return Ok(brand);
    }
    

    For your GetAll action :

    [HttpGet]
    public IActionResult<IEnumerable<Brand>> GetAll()
    {
        var brands = repository.GetAll().ToList(); 
    
        return Ok(brands);
    }
    

    This will tell the routing middleware which action to invoke. For actions that you want mapped to the base controller route (i.e. "api/brands"), just use the attribute without an overload. Such as [HttpGet], [HttpPost], [HttpDelete]. For the actions that have a route parameter then you can use [HttpGet("{id}")] and so forth depending on the HTTP method. Don't worry about defining the type of the parameter in the attribute route template. You define the parameter in your action's parameters. For instance:

    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(int id)
    {
        // Code here
    
        return Ok();
    }
    

    If you want to map a route to something like "api/brands/designers/2" then you would use a template like [HttpGet("designers/{id}")] to do so. Don't put a "/" before the designers.

    Edit : Forgot to mention, make sure your Startup.cs is properly configured for Web API routing. You can read the specifics on the ASP.NET Core 3.1 docs for what all the different options do. If you used the Web API template then it's probably fine but it's worth double checking as improperly configured endpoint routing can cause issues. Make sure you have the following in your Configure method in Startup.cs.

    app.UseRouting();
    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
    

    Make sure that app.UseRouting(); is called before app.UseEndpoints();