Multi Level Asp.NET routes - c#

I'm trying to create multiple level routes in asp.net core such as:
api/cities
api/cities/{id}
api/cities/date/{date}
The problem is, when I try and use anything longer than the api/cities/{id} I just get a 404.
My controller:
[Route("api/[controller]")]
public class CitiesController : Controller
{
private ICityRepository _repository;
public CitiesController(ICityRepository repository)
{
_repository = repository;
}
// GET: api/cities
[HttpGet]
public IEnumerable<City> Get()
{
IEnumerable<City> results = _repository.GetCities();
return results;
}
//api/cities/date/{date}
[HttpGet]
[Route("date/{date}")]
public IEnumerable<City> Get2(string date)
{
return _repository.GetCitiesByDate(date);
}
// GET api/cities/5
[HttpGet("{id: int}")]
public string Get(int id)
{
return "value";
}
}
What do I need to do to get longer routes to work under this controller?
Edit:
I see the documentation here: https://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
It says that you can have routes like:
/orders/1
/orders/pending
/orders/2013/06/16
And have all three route separately. But it doesn't seem to provide example for how you do that specifically.

I can see the problem with the route as you cannot have "/" as part of the string.
Try passing the date as 2013_06_16 not 2013/06/16 or change the route to have date/{year}/{month}/{day}
example:
[Route("api/[controller]")]
public class CitiesController : Controller
{
private ICityRepository _repository;
public CitiesController(ICityRepository repository)
{
_repository = repository;
}
// GET: api/cities
[HttpGet]
public IEnumerable<City> Get()
{
IEnumerable<City> results = _repository.GetCities();
return results;
}
//api/cities/date/2016/06/16
[HttpGet]
[Route("date/{year}/{month}/{day}")]
public IEnumerable<City> Get2(string year,string month,string day)
{
string date = year + "/" + month + "/" + day;
return _repository.GetCitiesByDate(date);
}
// GET api/cities/5
[Route("{id: int}")]
[HttpGet]
public string Get(int id)
{
return "value";
}
//api/cities/pending
[Route("{text}"]
[HttpGet]
public string Get(string text)
{
return "value";
}
}
Hope this help.

Related

Why swagger return "Failed to load api definition"?

This is my code.
I'm trying to overload GET with 2 function :
With one parameter
With two parameter
I'm getting Swagger error "Failed to load API definition". Why ?
[Route("api/[controller]")]
[ApiController]
public class HospitalizedController : ControllerBase
{
[HttpGet("")]
public string Get(string MedicID)
{
string jsonData;
string connString = gsUtils.GetDbConnectionString();
// dosomething
}
[HttpGet("")]
public string Get(string MedicID, string PatientID)
{
string jsonData;
string connString = gsUtils.GetDbConnectionString();
//do something
}
}
The error "Failed to load API definition" occurs because the two methods are on the same Route.
You can specify a more specific route to distinguish them, like this:
[Route("api/[controller]")]
[ApiController]
public class HospitalizedController : ControllerBase
{
[HttpGet]
[Route("{medicId}")]
public string Get(string medicID)
{
}
[HttpGet]
[Route("{medicId}/patients/{patientId}")]
public string Get(string medicID, string patientID)
{
}
}

.NET Core API : No route matches the supplied values

i'm making a Web API and i want to retrieve the object created by the CreateCommand Method.
To do that, I'm using the CreateAtRoute function to call the GetCommandById function ,with the id of the created Command as parameter, but i'm getting the following error:
" System.InvalidOperationException: No route matches the supplied
values."
This is my controller:
[Route("api/commands")]
[ApiController]
public class CommandsController : Controller
{
private readonly ICommanderRepo _repository;
private readonly IMapper _mapper;
public CommandsController(ICommanderRepo repository,IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
[HttpGet]
public ActionResult <IEnumerable<CommandReadDto>> GetAllCommands()
{
var commands = _repository.GetAllCommands();
return Ok(_mapper.Map<IEnumerable<CommandReadDto>>(commands));
}
[HttpGet("{id}")]
public ActionResult <CommandReadDto> GetCommandById(int id)
{
var command = _repository.GetCommandById(id);
if(command != null)
{
return Ok(_mapper.Map<CommandReadDto>(command));
}
return NotFound();
}
[HttpPost]
public ActionResult <CommandReadDto> CreateCommand(CommandCreateDto commandCreateDto)
{
var commandModel = _mapper.Map<Command>(commandCreateDto);
_repository.CreateCommand(commandModel);
_repository.SaveChanges();
var commandReadDto = _mapper.Map<CommandReadDto>(commandModel);
var x = nameof(GetCommandById);
return CreatedAtRoute(nameof(GetCommandById), new { id = commandReadDto.Id }, commandReadDto);
}
I have already tried this (which didn't resolve the problem):
Check if the parameters of both functions match
Added to my Startup.cs : services.AddControllers(options => options.SuppressAsyncSuffixInActionNames = false); ( i saw this on a post here in stackoverflow
What might be the problem?
When using CreatedAtRoute, you'll need something like shown below. Please note the addition of a route name, and use of that route name in CreatedAtRoute.
[HttpGet("{id}", Name="GetCommand")]
public ActionResult <CommandReadDto> GetCommandById(int id)
{
... // your code here
}
[HttpPost]
public ActionResult <CommandReadDto> CreateCommand(CommandCreateDto commandCreateDto)
{
... // your code here
return CreatedAtRoute("GetCommand", new { commandReadDto.Id }, commandReadDto);
}
An alternative, is to use CreatedAtAction like shown below. With this approach, a route name is not required.
return CreatedAtAction("GetCommandById", new { commandReadDto.Id }, commandReadDto);
You should derive your API-controllers from ControllerBase instead of Controller. The latter is targeting MVC-controllers.
And I believe you should remove options.SuppressAsyncSuffixInActionNames until you actually need it.

CreatedAtAction results in "No route matches the supplied values"

There are huge numbers of questions about the "No route matches the supplied values" error, but I have not yet found any solutions among the answers :(
Here is my controller:
[ApiVersion("0.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class WidgetsController : ControllerBase
{
private readonly IRepository _repository;
public WidgetsController(IRepository repository)
{
_repository = repository;
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Add([FromBody] AddWidgetRequest request)
{
WidgetDetails details;
try
{
details = request.ToWidgetDetails();
}
catch (AddWidgetRequest.BadRequest e)
{
return BadRequest(e.Message);
}
var id = _repository.AddWidget(details);
return CreatedAtAction(nameof(GetById), new {id = id}, details.WithId(id));
}
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
if (_repository.TryGetWidget(id, out var details))
{
return Ok(details.WithId(id));
}
else
{
return NotFound();
}
}
}
When POSTing to /api/v0/Widgets, the new entry is added to the database, but HTTP 500 is returned, with message "System.InvalidOperationException: No route matches the supplied values.". My code is almost identical to the example in https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1, I'm at a loss as to what the issue could be.
You need specify the api version in the CreatedAtAction method like below:
public IActionResult Add([FromBody] AddWidgetRequest request,ApiVersion version)
{
return CreatedAtAction(nameof(GetById), new { id = 1, version = version.ToString() }, details.WithId(id));
}

WebAPI Controller GetAll or Get some Id

I have a controller like this
[Route("api/Foo/MyController/{id}")]
public IHttpActionResult Get(int id)
{
//Code code
foo(id); // Foo accept Int or Null
}
This do work, if call api/Foo/MyController/1, but i needed call api/Foo/MyController like it "GetAll" the parameter id now is null and the stuff into controller return all, how go there?
You can add a new method and route:
[Route("api/Foo/MyController")]
public IHttpActionResult Get()
{
//Code code
}
Edit: to reuse the same method you could use optional parameters:
[Route("api/Foo/MyController/{id}")]
public IHttpActionResult Get(int? id)
{
if (id.HasValue)
{
// get by id
}
else
{
// get all
}
}
Why don't have 2 separate methods:
[Route("api/Foo/")]
public IHttpActionResult GetAll()
{
// code
}
[Route("api/Foo/{id}")]
public IHttpActionResult GetById(int id)
{
// code
}
For clarity (readability, maintainability, supportability).
You could do also a optional parameter:
[Route("api/Foo/MyController/{id}")]
public IHttpActionResult Get(int? id = null)
{
IQueryable foo = GetData();
if (id.HasValue)
{
foo = foo.Where(e => e.Id == id.Value);
}
//...
}

Multiple Get actions with different attribute routing?

If I design my controller in such a way:
public class ItemController : ApiController
{
[HttpGet]
[RoutePrefix("item/dosomething")]
public void DoSomething(Item item)
{ }
[HttpGet]
[RoutePrefix("item/dosomethingnicer")]
public void DoSomethingNicer(Item item)
{ }
[HttpGet]
[RoutePrefix("item/dosomethingelse")]
public void DoSomethingElse(Item item)
{ }
}
Would this work?
I would expect a structure more akin to this:
[RoutePrefix("item")]
public class ItemController : ApiController
{
[HttpGet]
[Route("dosomething")]
public void DoSomething(Item item)
{ }
[HttpGet]
[Route("dosomethingnicer")]
public void DoSomethingNicer(Item item)
{ }
[HttpGet]
[Route("dosomethingelse")]
public void DoSomethingElse(Item item)
{ }
}
I use Web Api 2 in this way in a lot of Controllers:
[HttpGet]
[Route("~/api/{version}/research/export")]
public IHttpActionResult Export(){
do stuff...
}
[HttpPost]
[Route("~/api/{version}/research/list")]
public IHttpActionResult List()
{
do stuff...
}
I use full api path description and it works with no problems.

Categories