I'm new to ASP.NET MVC, below is my api controller:
// API controller named Movie
[HttpGet]
public JsonResult Get()
{
....
}
[HttpGet]
public JsonResult Get([FromQuery]int id)
{
....
}
[HttpGet]
public JsonResult Get([FromQuery]string title, [FromQuery]string category)
{
....
}
then when I start the appication and routed to localhost/api/movie/123, it threw an exception which is Multiple actions were found that match the request
but only the first action method match, since there is only one parameter?
You have a route conflict because all those actions map to the same route and the route table does not know which to choose.
Routes need to be unique per action to avoid route conflicts.
In order for localhost/api/movie/123 to match Get(int id) action the route template would need to look like
//GET api/movie/123
[HttpGet("{id:int}")]
public JsonResult Get(int id) {
//....
}
Note the removal of the [FromQuery] and also the use of a route constraint {id:int} on the id route parameter
The second action should now not conflict with the first
//GET api/movie?title=someTitle&category=someCategory
[HttpGet]
public JsonResult Get([FromQuery]string title, [FromQuery]string category) {
//....
}
Reference Routing to controller actions in ASP.NET Core
Reference Routing in ASP.NET Core
Related
I'm trying to set a route of my controller while also be able to navigate the index without typing Index, here's what I tried:
My route configuration
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Try #1
// My controller
[Route("panel/admin")]
public class MyController...
// My index action
public IActionResult Index()...
Problem: This doesn't work, all the actions become accessible at panel/admin so I get an error saying Multiple actions matched.
Even when setting the route of my index action to Route(""), doesn't change anything.
Try #2
// My controller
[Route("panel/admin/[action]")]
public class MyController...
// My index action
[Route("")]
public IActionResult Index()...
Here, the index route doesn't change, it stays panel/admin/Index.
What I want
I want to be able to access my index action when navigating to panel/admin and I also want my other actions to work with just their method names like panel/admin/UsersList.
Complete controller
[Route("panel/admin/[action]")]
public class MyController
{
[Route("")]
public IActionResult Index()
{
return View();
}
public IActionResult UsersList()
{
var users = _db.Users.ToList();
return View(users);
}
// Other actions like UsersList
}
Thank you.
Reference Routing to controller actions in ASP.NET Core
With attribute routes you have to be very specific about desired routes to avoid route conflicts. Which also means that you will have to specify all the routes. Unlike convention-based routing.
Option #1
[Route("panel/admin")]
public class MyController {
[HttpGet]
[Route("")] //GET panel/admin
[Route("[action]")] //GET panel/admin/index
public IActionResult Index() {
return View();
}
[HttpGet]
[Route("[action]")] //GET panel/admin/UsersList
public IActionResult UsersList() {
var users = _db.Users.ToList();
return View(users);
}
// Other actions like UsersList
}
Option #2
[Route("panel/admin/[action]")]
public class MyController {
[HttpGet] //GET panel/admin/index
[Route("~/panel/admin")] //GET panel/admin
public IActionResult Index() {
return View();
}
[HttpGet] //GET panel/admin/UsersList
public IActionResult UsersList() {
var users = _db.Users.ToList();
return View(users);
}
// Other actions like UsersList
}
The tilde (~) in [Route("~/panel/admin")] overrides the route prefix on the controller.
Tip
While using multiple routes on actions can seem powerful, it's better
to keep your application's URL space simple and well-defined. Use
multiple routes on actions only where needed, for example to support
existing clients.
I'm developing an online marketplace system in ASP.NET MVC where Users are able to create their own shops in my website with their own unique address (e.g MySite.com/UserShop) like some social medias such as Instagram which users' pages would be like "Instagram.com/YourPage".
I wanna do something similar in ASP.NET MVC. This is my stucture:
I have multiple controllers such as Home, Panel and ... and I also have a controller named ShopController which I excepted its Index action method to show users' pages (shops). It's like this:
[RoutePrefix("")]
public class ShopController : Controller
{
[Route("{shopName}")]
public ActionResult Index(string shopName)
{
return View();
}
}
When I enter the some address http://MySite/UserPage it works fine, but when I want to open the urls of my own website like http://MySite/Panel - I get exception: Multiple controller types were found that match the URL.
I think I have to set order for controllers and action methods (First my own action methods, and then ShopController), I know it can be done within single controllers by using [Order] attribute, but I don't know how to do this across all controllers.
How can I fix this and make it work properly?
Because of the desired flexibility with the custom user routes, you will end up with route conflicts as the user route is too general, which will make it also match your other site routes and cause route conflicts.
Attribute routes are checked before convention-based routes, so the shop controller will catch all requests.
Convention based routes would need to be used in this case if you want user routes to be on the root of the site. This is because the order in which routes are added to the route table are important as general routes will match before more specialised/targeted routes.
Consider mixing attribute routing and convention-based routing where the custom user routes will use convention based routes while your other controllers will use attribute routing.
[RoutePrefix("Home")]
public class HomeController : Controller {
[HttpGet]
[Route("")] //GET home
[Route("~/", Name = "default")] // Site root
public ActionResult Index() {
return View();
}
[HttpGet]
[Route("contact")] //GET home/contact
[Route("~/contact")] //GET contact
public ActionResult Contact() {
return View();
}
[HttpGet]
[Route("about")] //GET home/about
[Route("~/about")] //GET about
public ActionResult About() {
return View();
}
//...other actions
}
[RoutePrefix("Panel")]
public class PanelController : Controller {
[HttpGet]
[Route("")] //GET panel
public ActionResult Index() {
return View();
}
[HttpGet]
[Route("another-action")] //GET panel/another-action
public ActionResult Other() {
return View();
}
//...other actions
}
The attribute routes, because they are registered before convention routes will match before the user defined routes, which can use convention-based routing
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//Attribute routing
routes.MapMvcAttributeRoutes();
//Convention based routing
//User route:
routes.MapRoute(
name: "UserRoot",
url: "{shopName}/{action}", //eg MySite.com/UserShop
defaults: new { controller = "Shop", action = "Index"}
);
//Catch-All InValid (NotFound) Routes
routes.MapRoute(
name: "NotFound",
url: "{*url}",
defaults: new { controller = "Error", action = "NotFound" }
);
}
}
The attribute routes would then need to be removed from the shop controller to avoid conflicts with the other site controllers
public class ShopController : Controller {
//eg MySite.com/UserShop
//eg MySite.com/UserShop/index
public ActionResult Index(string shopName) {
return View();
}
//eg MySite.com/UserShop/contact
public ActionResult Contact(string shopName) {
return View();
}
//eg MySite.com/UserShop/about
public ActionResult About(string shopName) {
return View();
}
//...other actions
}
So now calls to MySite.com/UserShop will be routed to the correct shop controller and still allow the site controllers to be accessed.
While it is however more labour intensive than the convention based user routes, that is the trade off to get the desired routing behavior.
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.
I am doing a Web API 2 application and I have controller named NCT_ProcessSettings and already I have two GET methods as below.
1. public IEnumerable<Process_Settings> Get()
2. public HttpResponseMessage Get(int id)
Now I want to have third one as below (Same as first one but inside I will write different logic).
3. public IEnumerable<Process_Settings> Get() //Compiler will confuse which to pick?
I tried as below.
[HttpGet]
[Route("GetGlobalSettings")]
public IEnumerable<NCT_Process_Settings> GetGlobalSettings()
{
return entityObject.NCT_Process_Settings.Where(c => c.project_id == 0).ToList();
}
Below is my angularcode to call api.
var url = '/api/NCT_ProcessSettings/GetGlobalSettings';
May I have some idea how to fix this? Any help would be appreciated?
Enable attribute routing in WebApiConfig.cs before convention-based routes.
config.MapHttpAttributeRoutes();
Next update controller to use routing attributes. (note the route prefix)
[RoutePrefix("api/NCT_ProcessSettings")]
public class NCT_ProcessSettingsController : ApiController {
//GET api/NCT_ProcessSettings
[HttpGet]
[Route("")]
public IEnumerable<Process_Settings> Get() { ... }
//GET api/NCT_ProcessSettings/5
[HttpGet]
[Route("{id:int}")]
public HttpResponseMessage Get(int id) { ... }
//GET api/NCT_ProcessSettings/GetGlobalSettings
[HttpGet]
[Route("GetGlobalSettings")]
public IEnumerable<NCT_Process_Settings> GetGlobalSettings() { ... }
}
Read up more documentation here Attribute Routing in ASP.NET Web API 2
Used Action Name attribute
[ActionName("Get")]
public IEnumerable<Process_Settings> Get1()//used any name here
{
}
I have a controller called HotelsController to insert and edit hotels.
It has the following setup (method implementation removed for simplicity):
[RoutePrefix("{member_id:int}/hotels")]
public class HotelsController : ApplicationController
{
[Route("delete/{id:int}", Name = NamedRoutes.HotelDelete)]
public ActionResult Delete(int id)
{
}
[Route("new", Name = NamedRoutes.HotelNew)]
public ActionResult New()
{
}
[HttpPost]
[ValidateInput(false)]
public ActionResult New(HotelDataEntry hotel)
{
}
[Route("edit/{id:int}", Name = NamedRoutes.HotelEdit)]
public ActionResult Edit(int id)
{
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(HotelDataEntry hotel)
{
}
}
As you can see the following routes are using attribute routing:
Delete
New (without parameters)
Edit (without parameters)
The following routes use no attribute routing:
New (with parameters)
Edit (with parameters)
The routing is setup in Global.asax.cs as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
routes.IgnoreRoute("{resource}.asmx/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
Routen.Standard.ToString(),
"{member_id}/{controller}/{action}/{id}",
new { action = "browse", id = UrlParameter.Optional },
new { id = AllowedIdsRegExOptional }
);
}
Problem: Attribute routing works. I can call the Edit action with http://localhost:54868/301011/hotels/edit but the form on that page should post to the same uri and call the action that uses no attribute routing. But instead the action using the attribute based routing is called again. Why?
The form is supplied with method="post". Do you have any idea why the convention based route is not used? Thank you for your help.
Edit: I tried to add [HttpGet] in front of the attribute-routed New and Edit actions. The result is that on posting the form ASP.NET shows an error that the route is invalid. So for some reasons, the convention based routing is not working on the controller.
It seems that you cannot use both (attribute-based and convention-based) routing techniques in the same controller.
So what I did to resolve the issue is to add attribute-based routes to the two "unreachable" action methods. The route of these methods is the same as the route of the actions with the same name, but the name of the route is different (since route-names must be unique).
[RoutePrefix("{member_id:int}/hotels")]
public class HotelsController : ApplicationController
{
[Route("delete/{id:int}", Name = NamedRoutes.HotelDelete)]
public ActionResult Delete(int id)
{
}
[Route("new", Name = NamedRoutes.HotelNew)]
public ActionResult New()
{
}
[HttpPost]
[ValidateInput(false)]
[Route("new", Name = NamedRoutes.HotelNewPost)]
public ActionResult New(HotelDataEntry hotel)
{
}
[Route("edit/{id:int}", Name = NamedRoutes.HotelEdit)]
public ActionResult Edit(int id)
{
}
[HttpPost]
[ValidateInput(false)]
[Route("edit/{id:int}", Name = NamedRoutes.HotelEditPost)]
public ActionResult Edit(HotelDataEntry hotel)
{
}
}