How to make valid route for {id}/visit? - c#

I am new at asp.core , so I try to make valid route to {id}/visits
My code:
[Produces("application/json")]
[Route("/Users")]
public class UserController
{
[HttpGet]
[Route("{id}/visits")]
public async Task<IActionResult> GetUser([FromRoute] long id)
{
throw new NotImplementedException()
}
}
But, at route {id} generated method the same:
// GET: /Users/5
[HttpGet("{id}")]
public async Task<IActionResult> GetUser([FromRoute] long id)
{
return Ok(user);
}
How to make route /Users/5/visits nethod?
What parameters at GetUser should I add?

Name the methods differently and use constraints to avoid route conflicts:
[Produces("application/json")]
[RoutePrefix("Users")] // different attribute here and not starting /slash
public class UserController
{
// Gets a specific user
[HttpGet]
[Route("{id:long}")] // Matches GET Users/5
public async Task<IActionResult> GetUser([FromRoute] long id)
{
// do what needs to be done
}
// Gets all visits from a specific user
[HttpGet]
[Route("{id:long}/visits")] // Matches GET Users/5/visits
public async Task<IActionResult> GetUserVisits([FromRoute] long id) // method name different
{
// do what needs to be done
}
}

Related

Webapi and normal methods in the same controller?

With the introduction of the Apicontroller attribute in asp.net core 2.1, I wonder how do I get the api and normal methods to work in the same controller.
[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> SaveOrder(SaveOrderModel model)
{
//...
}
public async Task<IActionResult> CustomerOrders()
{
if (!User.IsInRole("Customer"))
return Challenge();
var customer = await _workContext.CurrentCustomer();
var model = await orderModelFactory.PrepareCustomerOrderListModel();
return View(model);
}
}
I can call post method /api/order/saveorder but cannot run the https://example.com/order/customerorders.
It shows an exceptions: InvalidOperationException: Action
'.CustomerOrders ' does not have an attribute route. Action methods on
controllers annotated with ApiControllerAttribute must be attribute
routed.
If I remove [ApiController] and [Route("api/[controller]")] on the controller level and instead put on the method level, then it surely works. still don't know if there's any better hybrid solution for these methods as i want to use this new ApiController feature.
[Route("/api/controller/saveorder")]
public async Task<IActionResult> SaveOrder(SaveOrderModel model)
Any input is greatly appreciated.
You are saying, that you cannot call https://example.com/order/customerorders. In your [Route("api/[controller]")] you define, that all Methods inside this controller will be available at https://example.com/api/order/.
So to call your method, you need to call https://example.com/api/order/customerorders.
If you want to stay with https://example.com/order/customerorders, you need to put the [Route] attributes at your methods:
[ApiController]
public class OrderController : ControllerBase
{
[HttpPost("api/order")]
public async Task<IActionResult> SaveOrder(SaveOrderModel model)
{
...
}
[HttpGet("order/customerorders")]
public async Task<IActionResult> CustomerOrders()
{
if (!User.IsInRole("Customer"))
return Challenge();
var customer = await _workContext.CurrentCustomer();
var model = await orderModelFactory.PrepareCustomerOrderListModel();
return View(model);
}
}

Is it OK for multiple REST API methods to share same controller class?

Is it best practice to put each distinct Get method in its own controller class, or is it perfectly fine to have multiple (related and non-related) API methods in the same class, if the methods are very simple and uncomplicated.
E.g. these two API methods work fine in the same controller class, but would they be better off in their own class?
If so why?
public class TestController : ApiController
{
[HttpGet]
[Route("api/test/ping")]
public IHttpActionResult Ping()
{
try
{
return Ok("HELLO");
}
catch (Exception ex)
{
return Content(HttpStatusCode.InternalServerError, ex.Message);
}
}
[HttpGet]
[Route("api/test/echo/{message}")]
public IHttpActionResult Echo(string message)
{
try
{
return Ok(message);
}
catch (Exception ex)
{
return Content(HttpStatusCode.InternalServerError, ex.Message);
}
}
}
There is nothing stopping you from having multiple actions in a controller once their routes are distinct and do not cause route conflicts in the current or other controllers.
Take your provided example. You can take advantage of route prefixes for the controller to help with the organizing of similar routes
[RoutePrefix("api/test")]
public class TestController : ApiController {
//GET api/test/ping
[HttpGet] [Route("ping")]
public IHttpActionResult Ping() {
return Ok("HELLO");
}
//GET api/test/echo/hello%20world
[HttpGet] [Route("echo/{message}")]
public IHttpActionResult Echo(string message) {
if(message == null)
return BadRequest();
return Ok(message);
}
}
Personally I would put related API actions that work that do related work together in 1 single controller class.
In your given example it would be fine to put them together. Another example, say you have a Controller that handles all actions on a User model (Please note not entirely valid code, but hopefully you get the point):
[RoutePrefix("api/users")]
public class UserController: ApiController
{
[HttpGet]
public IHttpActionResult GetUsers()
{
// GET all users.
}
[HttpGet]
[Route("{id}")]
public IHttpActionResult GetUserById(int id)
{
// GET user by ID
}
[HttpPost]
public IHttpActionResult CreateUser()
{
// Create User
}
[HttpPut]
[Route("{id}")]
public IHttpActionResult UpdateUser()
{
// Update User
}
}
As you can see, all these actions work on the User model, so they fit together in a Controller class.

Web Api how to define 2 methods with same parameters

I'm new to Web API. Reading up on restful made me think it was based upon verbs and as such I was hoping that the logic would be as well.
If I want to create an API for Delete and Get, which have the same signature I'm told off .
[HttpGet]
public HttpResponseMessage Index(int id)
{
return Request.CreateResponse(HttpStatusCode.OK, GetValue());
}
[HttpDelete]
public HttpResponseMessage Index(int id)
{
//logic
return Request.CreateResponse(HttpStatusCode.OK, true);
}
I was hoping by specifying the different verb Web Api 2 would tell. But even if I update the delete to (note the void return type)
[HttpDelete]
public void Index(int id)
{
//logic
}
I am still told off as the member called index with the same parameter types already exist.
According to https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client it shows
Action HTTP method Relative URI
Get a product by ID GET /api/products/id
Create a new product POST /api/products
Update a product PUT /api/products/id
Delete a product DELETE /api/products/id
The Get, Put and Delete have the same URL. Sadly, they don't display the server side code, only the client.
Are my only options to:
1. Overload the method (in this example, seems like it would be hacking as it's not needed to perform the required task)
2. Give the method a different name (eg `Delete` instead of `Index`)
Or is there another way?
You have a syntax issue. You can use attribute routes to maintain same paths but the methods must have different names and structures or you will get compilation error like you already experienced.
Using the example from your question
Action HTTP method Relative URI
Get a product by ID GET /api/products/id
Create a new product POST /api/products
Update a product PUT /api/products/id
Delete a product DELETE /api/products/id
The following would be a controller that matches the above
[RoutePrefix("api/products")]
public class ProductsController : ApiController {
[HttpGet]
[Route("{id:int}")] //Matches GET api/products/1
public IHttpActionResult Get(int id) {
return Ok(GetValueById(id));
}
[HttpPost]
[Route("")] //Matches POST api/products
public IHttpActionResult Post([FromBody]Product model) {
//...code removed for brevity
}
[HttpPut]
[Route("{id:int}")] //Matches PUT api/products/1
public IHttpActionResult Put(int id, [FromBody]Product model) {
//...code removed for brevity
}
[HttpDelete]
[Route("{id:int}")] //Matches DELETE api/products/1
public IHttpActionResult Post(int id) {
//...code removed for brevity
}
}
You can use Route attribute on api methods, check below:
[HttpGet]
[Route("same")]
public IHttpActionResult get(int id)
{
return Ok();
}
[HttpDelete]
[Route("same")]
public IHttpActionResult delete(int id)
{
return Ok();
}
And set http method to get for get request and delete for delete request, similar for post/put.

Same url for overloaded controller methods

In my web api application, I want to enable clients to make requests, using the same path, but pass different type of parameters.
For example:
public class MyController : ApiController
{
[HttpDelete]
public IHttpActionResult Delete(int id) {..}
[HttpDelete]
public IHttpActionResult Delete2(Guid id) {..}
[HttpDelete]
public IHttpActionResult Delete3(string id) {..}
}
I want the url for each method to be similar, for example:
api/MyController/1
api/MyController/abc etc..
Is this possible? Iv'e tried alot of combinations with ActionName attribute and Routing configuration, but nothing seemed to work.
Thanks
You can use attribute routing for this. For example:
[RoutePrefix("MyController")]
public class MyController : ApiController
{
[HttpDelete]
[Route("delete/{id:int}")]
public IHttpActionResult Delete(int id) {..}
[HttpDelete]
[Route("delete/{id:guid}")]
public IHttpActionResult Delete2(Guid id) {..}
[HttpDelete]
[Route("delete/{id:alpha}")]
public IHttpActionResult Delete3(string id) {..}
}
If you do this then the request url will be:
http://yoursever/mycontroller/delete/123
http://yoursever/mycontroller/delete/abc
http://yoursever/mycontroller/delete/91c74f8f-d981-4ee1-ba36-3e9416bba202
You need to provide a Route with different parameter types for each of your methods:
[RoutePrefix("api/MyController")]
public class MyController : ApiController
{
[HttpDelete]
[Route("{id:int}", Order = 1)]
public IHttpActionResult Delete(int id) {..}
[HttpDelete]
[Route("{id:guid}", Order = 2)]
public IHttpActionResult Delete2(Guid id) {..}
[HttpDelete]
[Route("{id}", Order = 3)]
public IHttpActionResult Delete3(string id) {..}
}
Of course you have to enable attribute routing if you haven't already.
The Order property for the Route attribute ensures that the route templates are checked in the correct order so that an int value will not match the string route.
Yes, this is possible. Try setting the route as a decoration.
example:
[Route("DeleteThis/{id}")]
[HttpDelete]
public IHttpActionResult DeleteThis(int id)
{
return Ok();
}
[Route("NowDeleteThis/{name}")]
[HttpDelete]
public IHttpActionResult DeleteThis(string name)
{
return Ok();
}

ApiController same route for int or string URI params

I would like for my controllers to extent endpoints based on the data-type of the same variable name. For instance method A takes an int and method B takes a string. I don't want to declare a new route but rather for the routing mechanism to differentiate between ints and strings. Here is an example of what I mean.
The "ApiControllers" setup:
public class BaseApiController: ApiController
{
[HttpGet]
[Route("{controller}/{id:int}")]
public HttpResponseMessage GetEntity(int id){}
}
public class StringBaseApiController: BaseApiController
{
[HttpGet]
[Route("{controller}/{id:string}")]
public HttpResponseMessage GetEntity(string id){}
}
The "WebApionfig.cs" has the following route added:
config.Routes.MapHttpRoute(
"DefaultApi",
"{controller}/{id}",
new { id = RouteParameter.Optional }
);
I want to call "http://controller/1" and "http://controller/one" and get results. Instead i see the the multiple route exception.
You could the try the below possible solutions.
//Solution #1: If the string (id) has any numeric, it will not be caught.
//Only alphabets will be caught
public class StringBaseApiController: BaseApiController
{
[HttpGet]
[Route("{id:alpha}")]
public HttpResponseMessage GetEntity(string id){}
}
//Solution #2: If a seperate route for {id:Int} is already defined, then anything other than Integer will be caught here.
public class StringBaseApiController: BaseApiController
{
[HttpGet]
[Route("{id}")]
public HttpResponseMessage GetEntity(string id){}
}
Use only the string, and check inside if you got an int, or a string or any other thing and call the appropriate method.
public class StringBaseApiController: BaseApiController
{
[HttpGet]
[Route("{controller}/{id:string}")]
public HttpResponseMessage GetEntity(string id)
{
int a;
if(int.TryParse(id, out a))
{
return GetByInt(a);
}
return GetByString(id);
}
}

Categories