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.
Related
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);
}
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'm trying to get a REST service up and running (I followed this tutorial), and was trying to extend it with a simple method to mark one of the ToDoItem as "Complete"; literally to pass an ID into a method which should mark it as "Complete".
However, I'm struggling to understand how the routing works.
This is the method provided by default, which works correctly via https://localhost:44388/api/values
If I add another GET operation, even with different [Route] attribute, then I end up with "AmbiguousActionException: Multiple actions matched"
[Route("api/values")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
I tried to specify a route prefix using the method below, so that I could add doesn't work; I get a 404 on https://localhost:44388/api/values and https://localhost:44388/api/values/getbyname
[RoutePrefix("api/values")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[Route("getbyname")]
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
I might be trying the wrong method, so I'm happy to take any advice. I just want to be able to create new REST calls and have them the appropriate actions. Do I need to create other controllers? Am I limited to one GET/POST/PUT etc per controller?
Edit: didn't provide enough info, here's more code:
[Route("api/ToDo")]
[ApiController]
public class ToDoController : ControllerBase
{
private readonly ToDoContext _context;
public ToDoController(ToDoContext toDoContext)
{
_context = toDoContext;
if (_context.ToDoItems.Count() == 0)
{
//collection is empty, so add a new item
ToDoItem item1 = new ToDoItem(1, "example 1");
ToDoItem item2 = new ToDoItem(2, "example 2");
_context.ToDoItems.Add(item1);
_context.ToDoItems.Add(item2);
_context.SaveChanges();
}
}
//GET: api/todo
[HttpGet]
public async Task<ActionResult<IEnumerable<ToDoItem>>> GetToDoItems()
{
return await _context.ToDoItems.ToListAsync();
}
//GET: api/todo/5
//[HttpGet(Name = "Get a ToDoItem")]
//[Route("get{id}")]
[HttpGet("{id}")]
public async Task<ActionResult<ToDoItem>> GetToDoItem(long id)
{
var todoitem = await _context.ToDoItems.FindAsync(id);
if (todoitem == null)
{
return NotFound();
}
return todoitem;
}
//POST: api/Todo
[HttpPost]
public async Task<ActionResult<ToDoItem>> PostToDoItem(ToDoItem todoItem)
{
_context.ToDoItems.Add(todoItem);
await _context.SaveChangesAsync();
//calls the "GetToDoItem" method above!
return CreatedAtAction("GetToDoItem", new { id = todoItem.ID }, todoItem);
}
//DELETE: api/todo/5
[HttpDelete("{id}")]
public async Task<ActionResult<ToDoItem>> DeleteToDoItem(long id)
{
var todoItem = await _context.ToDoItems.FindAsync(id);
if(todoItem == null)
{
return NotFound();
}
_context.ToDoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return todoItem;
}
//* -. space to create a "MarkAsComplete" method
//GET: api/todo/5
[HttpGet(Name = "{name}")]
public async Task<ActionResult<ToDoItem>> MarkAsComplete(long id)
{
var todoitem = await _context.ToDoItems.FindAsync(id);
if (todoitem == null)
{
return NotFound();
}
else
{
todoitem.IsComplete = true;
}
return todoitem;
}
//*/
}
Mixing up different versions of the attributes. RoutePrefix is from a previous version.
Routes need to be unique per action to avoid route conflicts.
For example.
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase {
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get() {
return new string[] { "value1", "value2" };
}
// GET api/values/some_name
[HttpGet("{name}")]
public IActionResult GetByName(string name) {
return Ok();
}
}
Reference Routing to controller actions in ASP.NET Core
When building a REST API, it's rare that you will want to use [Route(...)] on an action method. It's better to use the more specific Http*Verb*Attributes to be precise about what your API supports. Clients of REST APIs are expected to know what paths and HTTP verbs map to specific logical operations.
Based on the additional details provided, that MarkAsComplete action should use HTTP PUT so signify that the model is being edited/updated.
For example
//* -. space to create a "MarkAsComplete" method
//PUT: api/todo/5
[HttpPut("{id:long}")]
public async Task<ActionResult<ToDoItem>> MarkAsComplete(long id) {
var todoitem = await _context.ToDoItems.FindAsync(id);
if (todoitem == null) {
return NotFound();
} else {
todoitem.IsComplete = true;
}
return todoitem;
}
//*/
This is my first question ever so please bear with me!
I am trying to create a multi-tenant application, where a backend accesses a different database depending on a client ID obtained from a jwt which also serves to authorize API calls.
Ideally I would like to be able to access the web token in my Startup.cs class and generate the connection string with it. Something like this:
public void ConfigureServices(IServiceCollection services)
{
// Stuff
// get jwt claims, something like:
// clientId = Request.Claims["ClientId"]; However Request only seems to be accessible within a controller
// newConnectionString = logic to replace the value found in Configuration.GetConnectionString("Default") with my desired database connection string
services.AddDbContext<KahunaDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Default"))); // current state
// options.UseSqlServer(newConnectionString)); // desired state
}
However, it doesn't seem to be possible to access the jwt outside of a controller.
I can't think of a way to make this happen without horribly messing up the repository pattern I have setup.
Here are some of my other classes which you might need to better understand where I am standing.
Base repository:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace API.Persistence.Repositories
{
public class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class
{
private readonly KahunaDbContext context;
public int ClienteId { get; set; }
public BaseRepository(KahunaDbContext context)
{
}
public void Crear(TEntity entity)
{
context.Set<TEntity>().Add(entity);
}
public async Task<List<TEntity>> GetTodos()
{
return await this.context.Set<TEntity>().ToListAsync();
}
public TEntity GetSingle(int id)
{
return this.context.Set<TEntity>().Find(id);
}
public void Borrar(TEntity entity)
{
this.context.Remove(entity);
}
}
public interface IBaseRepository<T>
{
int ClienteId { get; set; }
void Crear(T entity);
Task<List<T>> GetTodos();
void Borrar(T entity);
T GetSingle(int id);
}
}
Base controller:
using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using APIPersistence;
using API.Persistence.Repositories;
namespace API.Controllers
{
public class BaseController<TEntity, TEntityResource> : Controller
{
private readonly IMapper mapper;
private readonly IUnitOfWork unitOfWork;
private readonly IBaseRepository<TEntity> repository;
public BaseController(IBaseRepository<TEntity> repository, IUnitOfWork unitOfWork, IMapper mapper)
{
}
[HttpGet]
[Authorize]
virtual public async Task<IActionResult> Get()
{
List<TEntity> TEntitys = await this.repository.GetTodos();
// List<TEntityResource> TEntityResource = this.mapper.Map<List<TEntity>, List<TEntityResource>>(TEntitys);
return Ok(TEntitys);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] TEntityResource TEntityResource)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
TEntity tEntity = this.mapper.Map<TEntityResource, TEntity>(TEntityResource);
this.repository.Crear(tEntity);
await this.unitOfWork.CompleteAsync();
return Ok(tEntity);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
TEntity TEntity = this.repository.GetSingle(id);
this.repository.Borrar(TEntity);
await this.unitOfWork.CompleteAsync();
return Ok(true);
}
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, [FromBody] TEntityResource TEntityResource)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
TEntity TEntity = this.repository.GetSingle(id);
this.mapper.Map<TEntityResource, TEntity>(TEntityResource, TEntity);
await this.unitOfWork.CompleteAsync();
return Ok(true);
}
}
}
Even if you don't have time to answer the question in detail, any guidance will be extremely helpful. Please let me know if you need any additional information.
Cheers!