Attribute Routing failing - Web Api - asp.net

I have two actions on a controller called JobController that has this Route Prefix
[RoutePrefix("API/Job")]
1st Action (In order of precedence in controller)
[Route("{jobId}/{user}"), System.Web.Http.HttpPost]
public HttpResponseMessage AssignUser(long jobId, string user)
2nd Action
[HttpPost]
[Route("{id}/comment/")]
public HttpResponseMessage SaveComment(string commentText, long id)
Doing a post with Postman to this Route - MyDomain/API/Job/11/Comment - with a commentText value of "foo" matches against the first route, not the one i want it to.
Any ideas why this is happening?

Only guessing, but I think WebAPI cannot distinguish between route 1 and 2 because "comment" could also be a user-name. You could change the first route to something like:
[Route("{jobId}/users/{user}"), System.Web.Http.HttpPost]
public HttpResponseMessage AssignUser(long jobId, string user)

The problem here, is that both actions have the same action (POST), the same number of parameters and same type for these parameters, so which comes first that matches the route will win.
Your route need to have something unique distinguish between the two routes, so you need to change your routes to either have different actions (POST vs PUT for example) or you change one route to be something like this
[HttpPost]
[Route("{id}/comments/{comment}")]
public HttpResponseMessage SaveComment(long id, string comment)
hope this helps.

In the end I just created separate ViewModels
[HttpPost]
[Route("comment")]
public HttpResponseMessage SaveComment([FromBody] JobCommentViewModel viewModel)
public class JobCommentViewModel
{
public long JobId { get; set; }
public string Comment { get; set; }
}

Related

Passed data to asp.net core controller using ajax is null

I'm trying to call a asp.net core controller using ajax but the passed value is null.
I debug the javacript in order to check if the parameters in ajax is correct.
The OrderType is IR ID and the OrderKeyword is test
please see the screenshot
And this is the model
public class OrderSearchInfo
{
public int OrderType { get; set; }
public string OrderKeyword { get; set; }
}
And this is my controller
[HttpPost]
public async Task<ActionResult> SearchOrder([FromBody]OrderSearchInfo order)
{
return View();
}
while debugging and checking the value of the OrderSearchInfo order is null
What did i do wrong? And how can i solve this? Thank you
I assume there are some main points to check:
data types: in your model, there is int OrderType, but in JS you have OrderType: "IR_ID" which isn't a number and could not be mapped to int
if your form has RequestVerificationToken, you should pass it in the request's headers
routing (ensure request URL matches ASP.NET expected one
P.S. please add your suggestions for further checks in comments, to make this answer more applicable for many people.

.Net: Does controller have to have file name corresponding to URL?

I'm following this tutorial on .Net controllers, and it says "imagine that you enter the following URL into the address bar of your browser: http://localhost/Product/Index/3. In this case, a controller named ProductController is invoked."
What I Want To Know:
In order to successfully hit http://localhost/Product/Index/3, do you need a controller called ProductController specifically?
No, it is not necessary. You can use Route Attribute.
[Route("new-name-for-product")]
public class ProductController{
}
now you have to use http://localhost/new-name-for-product/Index/ this URL to invoke ProductController.
If you want to use one or more parameters with this URL you have to use different route templates for ActionMethod. Example below.
[Route("new-name-for-product")]
public class ProductController
{
// http://localhost/new-name-for-product/3/ will show product details based on id
// http://localhost/new-name-for-product/Index/3/ will show product details based on id
[HttpGet]
[Route("/{id}")]
public IActionResult Index(int id)
{
// your code
}
// you can use a different action method name.
// http://localhost/details/3/ will show product details based on id
// but parameter name (Ex: id) and the id inside route template the spelling must be the same.
[HttpGet]
[Route("details/{id}")]
public IActionResult GetById(int id)
{
// your code
}
}
It depends. In ASP.Net Core, routing can be configured either as conventional routing or as attribute routing.
Conventional routing is configured as below:
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
Here, the first path segment maps to the controller name,
the second maps to the action name,
the third segment is used for an optional id used to map to a model entity.
As a convention, controller file name is usually same as controller class name.
Hence, In conventional routing, url will match with filename.
The URL http://localhost/Products/Index matches below action method in ProductsController.
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpPost("Index")] // Matches 'Products/Index'
public IActionResult Index()
{
return View();
}
}
Attribute Routing
With attribute routing the controller name and action names play no role in which action is selected.
Hence, it is independent of file name.
The URL http://localhost/Items/All matches below action method in ProductsController.
public class ProductsController : Controller
{
[Route("Items/All")]
public IActionResult Index()
{
return View();
}
}
Similarly, [Route] attribute can be added at both Controller and action methods. The same URL http://localhost/Items/All matches the action method shown below:
[Route("Items")]
public class ProductsController : Controller
{
[Route("All")]
public IActionResult Index()
{
return View();
}
}
For more details, you can refer to microsoft docs at https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1

What's the difference between [HttpGet] and [HttpGet("{id}")]?

What are the differences between HTTPGET method and HTTPGET("{id}") method?
What is the API method that is used to update table columns?
[HttpGet]
public IActionResult Get()
{
return new JsonResult(users);
}
// GET api/values/5
[HttpGet("{id}")]
public IActionResult Get(string id)
{
return new JsonResult(response);
}
You should take a look at Attribute Routing in Web API.
The first method is routing to a clean api:
/api/controller
The second specifies route value in the attribute and will be called via the following url:
/api/controller/5
The second one is normally used to update existing queries, as you specify specific value in the route whilst the first specifies nothing.

Route all Web API requests to one controller method

Is it possible to customize ASP.NET Web API's routing mechanism to route all requests to the API to one controller method?
If a request comes in to
www.mysite.com/api/products/
or
www.mysite.com/api/otherResource/7
All would be routed to my SuperDuperController's Get() method?
I ran into a case where I needed to do this. (Web API 2)
I first looked into creating custom IHttpControllerSelector and IHttpActionSelectors. However, that was a bit of a murky way around. So I finally settled on this dead simple implementation. All you have to do is setup a wildcard route. Example:
public class SuperDuperController : ApiController
{
[Route("api/{*url}")]
public HttpResponseMessage Get()
{
// url information
Request.RequestUri
// route values, including "url"
Request.GetRouteData().Values
}
}
Any GET request that starts with "api/" will get routed to the above method. That includes the above mentioned URLs in your question. You will have to dig out information from the Request or context objects yourself since this circumvents automatic route value and model parsing.
The good thing about this is you can still use other controllers as well (as long as their routes don't start with "api/").
I don't konw why you would want to do this and I certainly wouldn't recommend routing everything through one controller, however you could achieve this as follows. Assuming you are only ever going to have a resource with an optional id in your calls, add this to your WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{resource}/{id}",
defaults: new { controller = "SuperDuper", id = RouteParameter.Optional }
);
}
}
Then define your controller method as follows:
public class SuperDuperController : ApiController
{
public IHttpActionResult Get(string resource, int? id = null)
{
return Ok();
}
}
You would need to decide on an appropriate IHttpActionResult to return for each different type of resource.
Alternatively using Attribute Routing, ensure that config.MapHttpAttributeRoutes() is present in your WebApiConfig and add the following attributes to your controller method:
[RoutePrefix("api")]
public class SuperDuperController : ApiController
{
[Route("{resource}/{id?}")]
public IHttpActionResult Get(string resource, int? id = null)
{
return Ok();
}
}

Ambigous Routes Using ASP.Net MVC

So far I still have the standard routing. What I tried to do is
public Foo : Controller
{
public ActionResult Index(int id)
{
return View("List", repo.GetForId(id));
}
public ActionResult Index()
{
return View("List", repo.GetAll());
}
}
The URL I entered was
localhost/Foo/Index.
I was under the asumption that it was smart enough to figure out which method I wanted. All I get though is an error telling me that the call is ambigous. From reading the route tutorial I thought that this would work. What am I missing?
Sorry:
Duplicate. I am voting to close.
Method overloading (two actions with the same name) resolution is based on the HTTP verb. This means that if you want to have two actions with the same name you need to differentiate them by the HTTP verb they accept:
public ActionResult Index(int id)
{
return View("List", repo.GetForId(id));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index()
{
return View("List", repo.GetAll());
}
Thus when you call GET /Home/Index the first action will be invoked and when you call POST /Home/Index the second one will be invoked.
Also note that you should probably use nullable integer as the id argument because if no parameter is passed in the request, the model binder will fail.
A RESTful way to handle this, following conventions that I would suggest you is this:
public ActionResult Index()
{
return View(repo.GetAll());
}
public ActionResult Show(int id)
{
return View(repo.GetForId(id));
}
which will allow you to handle both /Home/Index and /Home/Show/10 requests. You probably will have different views as well because the first one will be strongly typed to IEnumerable<T> while the second one to T.

Resources