I try to start asp.net core web api routing attribute as default route but when I access routing with parameter, I could not get any response
[Route("api/[controller]")]
[ApiController]
public class WarehousesController : ControllerBase
{
private readonly ApplicationDbContext _context;
public WarehousesController(ApplicationDbContext context)
{
_context = context;
}
//http://localhost:2394/api/Warehouses/Project/1 (Not working)
[HttpGet("Project/{projectId}")]
public async Task<IActionResult> GetWarehouseByProjectId([FromRoute] int projectId)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var warehouse = await _context.warehouses.Include(x => x.Projects).Where(y => y.Projects.FirstOrDefault().ProjectId == projectId).ToListAsync();
if (warehouse == null)
{
return NotFound();
}
return Ok(warehouse);
}
}
Try with this one .This works fine for me
[HttpGet("Project/{projectId}")]
public async Task<IActionResult> GetWarehouseByProjectId([FromRoute] int projectId)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid state");
}
var warehouse =await _context.warehouses.FindAsync(projectId);
if (warehouse == null)
{
return NotFound("not found");
}
return Ok(warehouse);
}
Related
As an exercise I was asked to moved logic from controller to service layer. With methods such as Get or GetById it wasn't a problem. Difficult part is to move Update and Patch methods. Below, you will find what I do have in Controler class and how I've changed them to the new requirement.
ToursController.cs before change
public class ToursController : ControllerBase
{
private readonly ToursRepository _repo;
private readonly IMapper _mapper;
public ToursController(ToursRepository repository, IMapper mapper)
{
_repo = repository ?? throw new ArgumentNullException(nameof(repository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
[HttpPut("{id}")]
public async Task<ActionResult> Update(int id, TourForUpdateDto updateDto)
{
var tour = await _repo.GetByIdAsync(id);
if (tour is null)
{
return NotFound();
}
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
return NoContent();
}
[HttpPatch("{id}")]
public async Task<ActionResult> Patch(int id, JsonPatchDocument patchDocument)
{
var tour = await _repo.GetByIdAsync(id);
if (tour is null)
{
return NotFound();
}
var updateDto = _mapper.Map<TourForUpdateDto>(tour);
patchDocument.ApplyTo(updateDto);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!TryValidateModel(patchDocument))
{
return BadRequest(ModelState);
}
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
return NoContent();
}
}
ToursController.cs after change
public class ToursController : ControllerBase
{
private readonly ToursRepository _repo;
private readonly IMapper _mapper;
private readonly ToursService _toursService;
public ToursController(ToursRepository repository, IMapper mapper)
{
_repo = repository ?? throw new ArgumentNullException(nameof(repository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_toursService = toursService ?? throw new ArgumentNullException(nameof(toursService));
}
[HttpPut("{id}")]
public async Task<ActionResult> Update(int id, TourForUpdateDto updateDto)
{
if (!_toursService.Exists(id))
{
return NotFound();
}
await _toursService.Update(id, updateDto);
return NoContent();
}
[HttpPatch("{id}")]
public async Task<ActionResult> Patch(int id, JsonPatchDocument patchDocument)
{
// No idea how to update this method.
var tour = await _repo.GetByIdAsync(id);
if (tour is null)
{
return NotFound();
}
var updateDto = _mapper.Map<TourForUpdateDto>(tour);
patchDocument.ApplyTo(updateDto);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!TryValidateModel(patchDocument))
{
return BadRequest(ModelState);
}
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
return NoContent();
}
}
ToursService.cs after change
using AutoMapper;
using BandManager.Api.Models;
using BandManager.Api.Services.Repositories;
public class ToursService
{
private readonly ToursRepository _repo;
private readonly IMapper _mapper;
public ToursService(ToursRepository repo, IMapper mapper)
{
_repo = repo ?? throw new ArgumentNullException(nameof(repo));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public async Task Update(int id, TourForUpdateDto updateDto)
{
var tour = await GetByIdAsync(id);
if (tour is null)
return;
_mapper.Map(updateDto, tour);
await _repo.SaveChangesAsync();
}
public async Task<TourDto?> GetByIdAsync(int id)
{
return _mapper.Map<TourDto>(await _repo.GetByIdAsync(id));
}
public bool Exists(int id)
{
return _repo.Exists(id);
}
}
Questions and thoughts
Should I ever return objects which are implementing IActionResult or any MVC related types from my services? My gut feeling is - no.
Should service be able to use AutoMapper to convert entities to DTOs? I think, yes.
Should my service, in the same method call, be able to call SaveChangesAsync on repository?
I created application, which works. Below I it is possible to see my example controller.
I want to create tests for controllers with authorization, but all with my ideas was bad. Please about advice how to create tests for controller in nunit/xunit, which is good for authorization. My problem is with creating suitable tests which are not equals to unauthorized, different problem is displaying information about errors with creating client. I would be grateful for help in solving this problem.
[Area("Admin")]
public class CategoryController : Controller
{
private readonly IUnitOfWork _unitOfWork;
public CategoryController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public IActionResult Index()
{
return View();
}
public IActionResult Upsert(int? id)
{
Category category = new();
if (id == null)
{
return View(category);
}
category = _unitOfWork.Category.Get(id.GetValueOrDefault());
if (category == null)
{
return NotFound();
}
return View(category);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Upsert(Category category)
{
if (ModelState.IsValid)
{
if (category.Id == 0)
{
_unitOfWork.Category.Add(category);
}
else
{
_unitOfWork.Category.Update(category);
}
_unitOfWork.Save();
return RedirectToAction(nameof(Index));
}
return View(category);
}
}```
I have controller's method which return mapped Dto. In case I find nothing in my Db (null) I want to return NotFound(), else I want to return Ok(item). But IDE shows me error (like in title). Code below:
[Route("api/[controller]")]
[ApiController]
public class ArticleController : ControllerBase
{
private readonly IArticleRepository _repository;
private readonly IMapper _mapper;
public ArticleController(IArticleRepository articleRepository,IMapper mapper)
{
_repository = articleRepository;
_mapper = mapper;
}
// GET: api/Article/5
[HttpGet("{id}", Name = "Get")]
public async Task<ArticleReadDto> Get(int id)
{
var item = await _repository.GetArticle(id);
if(item == null)
{
return NotFound(item);
}
var mapped_item = _mapper.Map<ArticleReadDto>(item);
return Ok(item);
}```
Change
public async Task<ArticleReadDto>
to:
async Task<IActonResult<ArticleReadDto>>
and
var mapped_item = _mapper.Map<ArticleReadDto>(item);
return Ok(item);
To
var mapped_item = _mapper.Map<ArticleReadDto>(item);
return Ok(mapped_item );
and
return NotFound(item);
to
return NotFound();
//or
return NotFound( new ArticleReadDto());
This is because you are calling the methods Ok and NotFound that are fulfilled by the ControllerBase class.
Documentation: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase?view=aspnetcore-5.0
The methods that you are using return an ActionResult: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.ok?view=aspnetcore-5.0#Microsoft_AspNetCore_Mvc_ControllerBase_Ok
If you wish to return these statuses then having a IActionResult return type might be more appropriate:
[HttpGet("{id}", Name = "Get")]
public async Task<IActionResult> Get(int id)
{
var item = await _repository.GetArticle(id);
if(item == null)
{
return NotFound(item);
}
var mapped_item = _mapper.Map<ArticleReadDto>(item);
return Ok(item);
}
I have created the following web service and can access it by:
https://localhost:44311/valores/1
but I want to access it with a url like:
https://localhost:44311/usuario/1
using usuario in the url
[HttpGet("{id:int}",Name ="usuario")]
public ActionResult<Usuario> Get(int id)
{
using (var db = new prueba2Context())
{
var usuario = db.Usuario.Where(x => x.Id == id).FirstOrDefault();
if (usuario == null)
{
return NotFound();
}
return Ok(usuario);
}
}
I am new to c#, I appreciate if you indicate what I am doing wrong and how to correct it.
This is the structure of my folder.
It looks like you are using ASP.NET Core. A typical endpoint will be set up like this:
[ApiController, Route("api/[controller]")]
public class ComputationController : ControllerBase
{
// ...
[HttpPost, Route("beginComputation")]
[ProducesResponseType(StatusCodes.Status202Accepted, Type = typeof(JobCreatedModel))]
public async Task<IActionResult> BeginComputation([FromBody] JobParametersModel obj)
{
return Accepted(
await _queuedBackgroundService.PostWorkItemAsync(obj).ConfigureAwait(false));
}
[HttpGet, Route("computationStatus/{jobId}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(JobModel))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(string))]
public async Task<IActionResult> GetComputationResultAsync(string jobId)
{
var job = await _computationJobStatusService.GetJobAsync(jobId).ConfigureAwait(false);
if(job != null)
{
return Ok(job);
}
return NotFound($"Job with ID `{jobId}` not found");
}
// ...
}
The [ProducesResponseType] attribute is for documentation frameworks such as Swagger.
I always use the [Route] attribute to define the endpoint path.
In your case, I would set it up as so:
[HttpGet, Route("usuario/{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Usuario))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetUser(int id)
{
using (var db = new prueba2Context())
{
var usuario = await db.Usuario.Where(x => x.Id == id).FirstOrDefault();
if (usuario == null)
{
return NotFound();
}
return Ok(usuario);
}
}
Is more common use "Route"
like that
`[Route("usuario")]
public ActionResult<Usuario> Get(int id)
{
using (var db = new prueba2Context())
{
var usuario = db.Usuario.Where(x => x.Id == id).FirstOrDefault();
if (usuario == null)
{
return NotFound();
}
return Ok(usuario);
}
}
`
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;
}
//*/