The idea is that with these ItemController methods I should be able to:
Get the items by barcode when hitting "item/query?barcode={barcode}"
Get the items by discount when hitting "item/query?discount={discount}"
Get the items by name when hitting "item/query?name={name}"
Get the items by category when hitting "item/query?category={category}"
And this is my code for actions under the GET verb:
[HttpGet("query/{barcode:int}")]
public async Task<IActionResult> GetByBarcode(int barcode)
{
var item = ...
return Ok(item);
}
[HttpGet("query/{discount:int}")]
public async Task<IActionResult> GetByDiscount(int discount)
{
var items = ...
return Ok(items);
}
[HttpGet("query/{name}")]
public async Task<IActionResult> GetByName(string name)
{
var items = ...
return Ok(items);
}
[HttpGet("query/{category}")]
public async Task<IActionResult> GetByCategory(string category)
{
var items = ...
return Ok(items);
}
The problem is that I keep getting 405 Method Not Allowed when I try to access to any of those actions. I don't know if it's a matter of ambiguity between the methods, can you help me out?
Edit:
Header of ItemController.cs:
using InventoryWebApi.DataAccess;
using InventoryWebApi.DTO;
using InventoryWebApi.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace InventoryWebApi.Controllers
{
[ApiController]
[Route("[controller]")]
public class ItemController : ControllerBase
{
private readonly IRepository repository;
public ItemController(IRepository repository)
...
you are using routing a wrong way.
Your code will not be even run,
your actions shoud be
[HttpGet("query/getbybarcode/{barcode:int})]
public async Task<IActionResult> GetByBarcode(int barcode)
[HttpGet("query/getbydiscount/{discount:int}")]
public async Task<IActionResult> GetByDiscount(int discount)
For this routes you have to use this urls
.../item/query/getbybarcode/{barcode}
..../item/query/getbydiscount/{discount}
if you still want to create url your way you will have to change attribute routing
[HttpGet("query/getbybarcode)]
public async Task<IActionResult> GetByBarcode(int barcode)
[HttpGet("query/getbydiscount")]
public async Task<IActionResult> GetByDiscount(int discount)
and your url should be
.../item/query/getbydiscount?discount={discount}"
..../item/query/getbybarcode?barcode={barcode}"
Update
If you still need to use all your urls for your student project , then the only way is to use one action for all your urls
[HttpGet("query")]
public async Task<IActionResult> Get(int? barcode, int? discount,
string name, string category )
{
if (barcode != null) ...
else if (discount!=null) ...
var item = ...
return Ok(item);
}
Related
I am trying to make an api using asp.net core web api. Although I put [HttpGet] and [HttpPost] tags above the methods, only the function that takes parameters appears in the interface.
The methods I created do not appear in the swagger interface
namespace APIproje.Controllers {
public class UsersController : ControllerBase {
private readonly IKullnicilar _userRepositories;
public UsersController(IKullnicilar userRepositories) {
_userRepositories = userRepositories;
}
[HttpGet]
public async Task<IEnumerable<Users>> GetUsers() {
return await _userRepositories.Get();
}
[HttpGet("{id}")]
public async Task<ActionResult<Users>> GetUsers(int id) {
return await _userRepositories.Get(id);
}
[HttpPost]
public async Task<ActionResult<Users>> PostUsers([FromBody] Users user)
{
var newUser = await _userRepositories.Create(user);
return CreatedAtAction(nameof(GetUsers), new { id = newUser.id }, newUser);
}
}
}
Running:
Where am I wrong?
you can use
[ApiController]
[Route("you'r route")]
like this :
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace test
{
[ApiController]
[Route("api/")]
public class UsersController : ControllerBase
{
[HttpGet]
public async Task<IEnumerable<object>> GetUsers()
{
return new List<object>();
}
[HttpGet("{id}")]
public async Task<ActionResult<object>> GetUsers(int id)
{
return Ok(new object());
}
[HttpPost]
public async Task<ActionResult<object>> PostUsers([FromBody] object user)
{
return Ok(new object());
}
}
}
I have a WebApi using .NET core 3.1. I'm not sure if this isn't best practice or if there's another way to do this. But, I have a WebApi that will have 3 get verbs.
1. Get All.
2. Get by Id.
3. Get by a search string.
It work if I have just 2, the get all and get by id but once I had a 3rd, get by string I get an error. Then when I go to test in swagger I get,
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The
request matched multiple endpoints. Matches:
Code:
[ApiController]
[Route("api/v1/[controller]")]
public class ProfessionalLocalController : ControllerBase
{
private IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public ProfessionalLocalController(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var profLocals = await _unitOfWork.ProfessionalLocalUsers.GetAllAsync();
return Ok(_mapper.Map<IEnumerable<ProfessionalLocalDto>>(profLocals));
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var localUser = await _unitOfWork.ProfessionalLocalUsers.GetAsync(id);
if (localUser == null)
{
return NotFound();
}
return Ok(_mapper.Map<ProfessionalLocalDto>(localUser));
}
[HttpGet("{searchText}", Name = "Search")]
public async Task<IActionResult> Get(string searchText)
{
var localUsers = await _unitOfWork.ProfessionalLocalUsers.FindAsync(temp => temp.UserID.ToString().Contains(searchText));
return Ok(_mapper.Map<IEnumerable<ProfessionalLocalDto>>(localUsers));
}
}
Routes need to be unique when mapped to controller actions.
In this case the routes [HttpGet("{id}")] and [HttpGet("{searchText}", Name = "Search")] conflict because to both map to the same URI.
api/v1/ProfessionalLocal/{some value here}
If you want to keep the same URI as before, and allow for the the two action to be distinct, use a route constraint.
//GET api/v1/ProfessionalLocal/1234
[HttpGet("{id:int}")] //<-- note the constraint on the id
public async Task<IActionResult> Get(int id) {
//...
}
So that the above is called only when a valid value is provided. An integer in this case
And consider changing the route template of the search to be more descriptive of what it actually does.
//GET api/v1/ProfessionalLocal/search/{search text here}
[HttpGet("search/{searchText}", Name = "Search")]
public async Task<IActionResult> Get(string searchText) {
//...
}
Reference Routing to controller actions in ASP.NET Core
Reference Routing in ASP.NET Core
You can try changing it like below:
[HttpGet]
[Route("users")]
public async Task<IActionResult> Get()
{
var profLocals = await _unitOfWork.ProfessionalLocalUsers.GetAllAsync();
return Ok(_mapper.Map<IEnumerable<ProfessionalLocalDto>>(profLocals));
}
[HttpGet("{id}")]
[Route("users/{id}")]
public async Task<IActionResult> Get(int id)
{
var localUser = await _unitOfWork.ProfessionalLocalUsers.GetAsync(id);
if (localUser == null)
{
return NotFound();
}
return Ok(_mapper.Map<ProfessionalLocalDto>(localUser));
}
[HttpGet("{searchText}", Name = "Search")]
[Route("usersbytext/{id}")]
public async Task<IActionResult> Get(string searchText)
{
var localUsers = await _unitOfWork.ProfessionalLocalUsers.FindAsync(temp => temp.UserID.ToString().Contains(searchText));
return Ok(_mapper.Map<IEnumerable<ProfessionalLocalDto>>(localUsers));
}
Add a custom route for third one.
The default routing is unable to distinguish [Route("users/{id}")] from [Route("users/{id}")] making it ambigous.
I ran into a similar problem recently. My solution was to use the same method for GetAll and Search. What I did was:
Check for the query parameter, if the query parameter is available, return search results else return all. Please refer to the snippet below.
[HttpGet]
public async Task<IActionResult> Get([FromQuery]string search)
{
if(!String.IsNullOrEmpty(search))
// return search result
//return all
}
I use this
[HttpGet("[action]")]
public async Task<IActionResult> GetAll()
{
var profLocals = await _unitOfWork.ProfessionalLocalUsers.GetAllAsync();
return Ok(_mapper.Map<IEnumerable<ProfessionalLocalDto>>(profLocals));
}
I've been trying to make a REST API for a project. Never done this before technically, but one thing has thrown me for a bit of a baffle. I have a GET request to the server that properly returns a list of clients from my DbContext, but the second get action request I have in right now seems to only return an HTTP 405 error and I can't seem to find out as to why. Any ideas?
I've tried making it an actionResult, perhaps not properly? I know it functions when the action is just a string that returns "value" or another string, but doesn't seem to be able to return an object like the first GET method does
Working get method that gets all the clients in my repository
// GET: api/Client
[HttpGet]
public IEnumerable<Logic.Objects.Client> Get()
{
return _repository.GetClients();
}
Second method that is returning a 405
//GET: api/Client/5
[HttpGet("{id}", Name = "Get")]
public Task<Client> Get(Guid id)
{
return _repository.GetClientByIDAsync(id);
}
Expected results would be if I added a client ID to api/Client/{id}, then it would return that specific client's information, rather than all of them, since that is a unique identifier and wont be the same as any other client, keeping with the RESTful mindset.
405 is the only error it throws unless its an incorrect ID that isnt a Guid, in which case it throw an exception within the method, which also lets me know that it IS getting there, something is just wrong with how it should be returning it's values?
EDIT: This is the whole controller class for those interested. Some methods just arent implemented yet, so they aren't really pertinent to the problem. At least I wouldnt think.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dbnd.Logic.Interfaces;
using Dbnd.Logic.Objects;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Dbnd.Logic.Objects;
namespace Dbnd.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ClientController : ControllerBase
{
private IRepository _repository;
public ClientController(IRepository repository)
{
_repository = repository;
}
// GET: api/Client
[HttpGet]
public IEnumerable<Logic.Objects.Client> Get()
{
return _repository.GetClients();
}
//GET: api/Client/5
[HttpGet("{id}", Name = "Get")]
public Task<Client> Get(Guid id)
{
return _repository.GetClientByIDAsync(id);
}
// POST: api/Client
[HttpPost]
public ActionResult Post([FromBody, Bind("UserName, Email, PasswordHash")] Client client)
{
_repository.CreateClientAsync(client.UserName, client.Email, client.PasswordHash);
return CreatedAtRoute("Get", client);
}
// PUT: api/Client/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE: api/ApiWithActions/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
I have another api that works exactly the way you first posted (with actionResults). I couldn't find the problem in your code. It could be settings as you said, but it could also be the way you assemble your URL.
My best bet would be that your repository isn't returning the elemement but again, it should not give you a 405 but instead a 404 not found. I guess the response from your _repository is giving an error.
Maybe a troubleshooting idea is try a Postman request to get and a breakpoint in your controller to see if you could see where the problem is.
I just created a test project to try and reproduce your problem, but it works fine. I followed this tutorial from Microsoft: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-3.0&tabs=visual-studio
And here is my resulting controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ClientsController : ControllerBase
{
private readonly ClientContext _context;
public ClientsController(ClientContext context)
{
_context = context;
}
// GET: api/Clients
[HttpGet]
public async Task<ActionResult<IEnumerable<Client>>> GetClients()
{
return await _context.Clients.ToListAsync();
}
// GET: api/Clients/5
[HttpGet("{id}")]
public async Task<ActionResult<Client>> GetClient(long id)
{
var client = await _context.Clients.FindAsync(id);
if (client == null)
{
return NotFound();
}
return client;
}
// PUT: api/Clients/5
[HttpPut("{id}")]
public async Task<IActionResult> PutClient(long id, Client client)
{
if (id != client.Id)
{
return BadRequest();
}
_context.Entry(client).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ClientExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Clients
[HttpPost]
public async Task<ActionResult<Client>> PostClient(Client client)
{
_context.Clients.Add(client);
await _context.SaveChangesAsync();
return CreatedAtAction("GetClient", new { id = client.Id }, client);
}
// DELETE: api/Clients/5
[HttpDelete("{id}")]
public async Task<ActionResult<Client>> DeleteClient(long id)
{
var client = await _context.Clients.FindAsync(id);
if (client == null)
{
return NotFound();
}
_context.Clients.Remove(client);
await _context.SaveChangesAsync();
return client;
}
private bool ClientExists(long id)
{
return _context.Clients.Any(e => e.Id == id);
}
}
}
I know it does not answer directly your question, but I hope it gives you food for thought and helps you with troubleshooting.
I have a controller that should return full urls of the other Actions in the same controller. I dont know how I can do this. Please look at my example (using pseudo results):
[Route("api/v{api-version:apiVersion}/Tests")]
[ApiController][AllowAnonymous]
public class TestsController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetActions()
{
var idDemo = 1;
//TODO
urls.Add("Get full url for Call method with parameter : idDemo ");
urls.Add("Get full url for Something method with parameter : idDemo ");
return Ok(urls);
}
[HttpPost("call/{id}")]
public async Task<IActionResult> Call(int id)
{
return Ok();
}
[HttpPost("do/it/{id}")]
public async Task<IActionResult> Something(int id)
{
return Ok();
}
}
I'm getting an array returned from an API call. The array looks something like this https://i.imgur.com/Rq8GfBI.png.
I get the array, then call my controller method using this
this.http.post<Maps[]>(this.baseUrl + "api/Map/InsertMap/", beatmaps[0]).subscribe();
Maps being an interface
interface Maps {
Id: number;
Name: string;
Artist: string;
Creator: string;
}
Now I just have a basic insert controller method
[Route("api/[controller]/InsertMap/")]
[HttpPost("[action]")]
public async Task<IActionResult> AsyncCreateMap(MapModel model)
{
await _mapService.AsyncInsert(model);
return Ok();
}
It takes in the Model as a parameter and then inserts it using Entity Framework. It doesn't work. I have no idea how to actually transfer the array I get to an object I can use in my controller.
Here is my whole controller class
[Route("api/[controller]")]
public class MapController : Controller
{
private readonly MapService _mapService;
public MapController(MapService mapService)
{
_mapService = mapService;
}
[Route("api/[controller]/Maps")]
[HttpGet("[action]")]
public async Task<IActionResult> AsyncMaps()
{
var data = await _mapService.AsyncGetMaps(0, 10);
return Ok(data);
}
[HttpPost]
public async Task<IActionResult> AsyncCreateMap([FromBody]MapModel model)
{
await _mapService.AsyncInsert(model);
return Ok();
}
}
you should try use [FromBody].
Example:
public class ModelDTO
{
public string Id{get; set;}
public List<string> Childs {get; set;}
}
[HttpPost]
[Route("api/nice/Save")]
public bool Save([FromBody] ModelDTO model)
{ ...
in the angular side, you should use httpClient.post..
save(data: IData): Observable<ISaveCompleted> {
const options = this.createPostOptions();
const saveCompleted = this.http
.post(options.url, data, options)
.map((res: Response) => <ISaveCompleted>res.json());
return saveCompleted;
}