CreatedAtAction results in "No route matches the supplied values" - c#

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));
}

Related

.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.

How do I set the status code with custom message in asp.net core 2.1?

I am working on asp.net core version 2.1, I have created a sample API project which works fine but I am unable to modify the status code with a custom message for example:
In Postman:
200 OK
Expecting:
200 Custom_Message
The code that I tried:
[HttpGet]
public IActionResult Get()
{
Response.StatusCode = 200;
Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = "Custom_Message";
return Ok();
}
Postman's current Output:
GitHub Repository
I think you should create your custom class:
public class CustomResult : OkResult
{
private readonly string Reason;
public CustomResult(string reason) : base()
{
Reason = reason;
}
public override void ExecuteResult(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.HttpContext.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = Reason;
context.HttpContext.Response.StatusCode = StatusCode;
}
}
Then in your controller method:
[HttpGet]
public IActionResult Get()
{
return new CustomResult("Custom reason phrase");
}
Output
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public abstract class BaseApiController : ControllerBase
{
public override OkResult Ok()
{
return base.Ok();
}
public override OkObjectResult Ok([ActionResultObjectValue] object value)
{
return base.Ok(new Response
{
Result = value,
Status = ResultStatus.Success,
Message = string.Empty
});
}
}

How to call api with controller's method

I want to call an ASP.NET Core 2.1.0 Web API with a controller's method.
I tried following but I get an error
Cannot GET /api/remote/NewAc/test1
Code:
[Route("api/remote/{action}")]
//[Route("api/[controller]")]
[ApiController]
public class RemoteController : ControllerBase
{
private readonly MyContext _context;
public RemoteValsController(MyContext context)
{ _context = context; }
[HttpGet]
public async Task<OkObjectResult> NewAc()
{
var r = await _context.TypeOfAccounts.AnyAsync();
return Ok(new { r = true });
}
[HttpGet("{id}")]
public async Task<OkObjectResult> NewAc([FromRoute] string AccountType)
{
var r = await _context.TypeOfAccounts.AnyAsync(o => o.AccountType.ToUpper() == AccountType.ToUpper());
return Ok(new { r = !r });
}
}
Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
I tried both [HttpPost] and [HttpGet] but in vain.
Re-check the defined routes for the controller.
[Route("api/remote/[action]")] //<-- NOTE Token replacement in route templates
[ApiController]
public class RemoteController : ControllerBase {
private readonly MyContext _context;
public RemoteController(MyContext context) {
_context = context;
}
//GET api/remote/NewAc
[HttpGet]
public async Task<IActionResult> NewAc() {
var r = await _context.TypeOfAccounts.AnyAsync();
return Ok(new { r = true });
}
//GET api/remote/NewAc/test1
[HttpGet("{accountType}")]
public async Task<IActionResult> NewAc(string accountType) {
var r = await _context.TypeOfAccounts.AnyAsync(o => o.AccountType.ToUpper() == accountType.ToUpper());
return Ok(new { r = !r });
}
}
Reference Routing to controller actions in ASP.NET Core
First, mapped routes and attribute routing are an either/or affair. If you have route attributes involved, the route definition in your Startup is not applicable at all.
Second, you can't just throw [FromRoute] in front a param and magically have it in the route. In fact, that attribute isn't necessary at all unless there's some ambiguity about where the param value is actually supposed to come from. If you want it from the route, then it needs to be part of your route template. Simply:
[HttpGet("{id}/{AccountType}")]
public async Task<OkObjectResult> NewAc(string AccountType)
{
var r = await _context.TypeOfAccounts.AnyAsync(o => o.AccountType.ToUpper() == AccountType.ToUpper());
return Ok(new { r = !r });
}

MongoDB / Asp.Net Core remove method error with "filter"

I am working on a Url Shortener in Asp.Net Core and using MongoDB.
I currently have a working Get method and a working Create method.
I ran into an issue with my Delete method and this is the message I get:
Argument 1: cannot convert from
'MongoDB.Driver.FilterDefinition)', candidates
are: System.Threading.Tasks.Task
DeleteOneAsync(MongoDB.Driver.FilterDefinition,
System.Threading.CancellationToken)(in interface
IMongoCollection)
System.Threading.Tasks.Task
DeleteOneAsync(this
MongoDB.Driver.IMongoCollection,
System.Linq.Expressions.Expression>,
System.Threading.CancellationToken) (in class
IMongoCollectionExtensions)
The error has something to do with this ".DeleteOneAsync(filter);" in my 'ShortUrlRepository' class:
public async Task<ShortUrl> Delete(ShortUrl su)
{
var filter = Builders<Url>.Filter.Eq("Su", su);
return await _db.Urls.DeleteOneAsync(filter);
}
My ShortUrlsController class seems to be working just fine but I will post it in case:
namespace ShortenUrls.Controllers
{
[Route("api/codes")]
public class ShortUrlsController : Controller
{
private readonly ShortUrlRepository _repo;
//private readonly IShortUrlService _service;
public ShortUrlsController(ShortUrlRepository repo /*IShortUrlService service*/)
{
_repo = repo;
//_service = service;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
var su = await _repo.GetAsync(id);
if (su == null)
return NotFound();
return Ok(su);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] ShortUrl su)
{
await _repo.CreateAsync(su);
return Ok(su);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete (ShortUrl su)
{
try
{
if (su == null)
return new NotFoundResult();
await _repo.Delete(su);
return Ok("Deleted Successfully");
}
catch (Exception ex)
{
return BadRequest(ex.ToString());
}
}
}
}
I have tried other remove methods but have gotten similar errors so maybe I am missing something?
If anyone can offer some suggestions I would greatly appreciate it as I am new to Asp.Net Core and I am having very little success finding a solution to this error. If I can provide anymore information please let me know.
Creating the variable 'r' and returning it solved the 'Argument 1 error':
public async Task<bool> Delete(ObjectId id)
{
var filter = Builders<ShortUrl>.Filter.Eq(x => x.Id, id);
var r = await _db.Urls.DeleteOneAsync(filter);
return r.DeletedCount > 0;
}
I made other changes that were unrelated to this error but were necessary to make the Delete method work properly. Here are the changes I had to make to my 'ShortUrlsController' class:
[HttpDelete("{id}")]
public async Task<IActionResult> Delete (string id)
{
return (await _repo.Delete(ObjectId.Parse(id)))
? (IActionResult) Ok("Deleted Successfully")
: NotFound();
}

Multi Level Asp.NET routes

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.

Categories