As naming convention says, WebApi controller actions name should be Get(), Put(). Post() etc. But tell me if I have a controller as CustomerController, now I want to have two actions inside of it. One is GetCustomerById(int id) and another one is GetCustomerByAge(int age). Here both the actions accept one parameter as int.
So, if I want to make the url user friendly like "api/customer/" also I want to follow the actions naming convention like only Get(int id)/Get(int age), how will I do it?
If you want Web Api to look for the action name when routing, change the WebApiConfig.cs class in the App_Start folder to below:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then you can just make a GET request to
http://mysite/api/customer/GetCustomerById/1
Also I recommend you to study the article below for deeper understanding:
Routing by Action Name
Restful services should not contain CRUD function names in the URI (https://restfulapi.net/resource-naming/)
this would more appropriate:
for GetById -
http://mysite/api/customers/123
for GetByAge - http://mysite/api/customers?age=21
An alternative way is HTTP Methods attribute.
Instead of using the naming convention for HTTP methods, you can explicitly specify the HTTP method for an action by decorating the action method with the HttpGet, HttpPut, HttpPost, or HttpDelete attribute.
In the following example, the FindProduct method is mapped to GET requests:
public class ProductsController : ApiController
{
[HttpGet]
public Product FindProduct(id) {}
}
To allow multiple HTTP methods for an action, or to allow HTTP methods other than GET, PUT, POST, and DELETE, use the AcceptVerbs attribute, which takes a list of HTTP methods.
public class ProductsController : ApiController
{
[AcceptVerbs("GET", "HEAD")]
public Product FindProduct(id) { }
}
This is one of those situations where following standards to the letter may not actually help you much.
One solution would be to allow yourself to deviate from the REST style.
You could have two get methods:
one could be GetByID, another could be GetByAge.
Your routes could look like this:
api/customer/getbyage/20
api/customer/getbyid/1134
This isn't exactly REST but it's close enough and one exception won't break anything.
My point is to use whatever implementation helps your product make sense and don't worry too much about standards.
Related
Here is my controller
public class SpecializationsController : Controller
{
public ActionResult Action1()
{
//body
}
public ActionResult Action2()
{
//body
}
Default url for Action1 is of course /Specialization/Action1. I want to add prefix to all Actions in my controller to make my ulr like /prefix/Specialization/Action1.
I tried to add [RoutePrefix("prefix")] to my controller but it doesn't work. I would like to avoid adding [Route] attribute for each action in my controller. So how can I add this prefix?
I would create Areas:
Areas are an ASP.NET MVC feature used to organize related functionality into a group as a separate namespace (for routing) and folder structure (for views). Using areas creates a hierarchy for the purpose of routing by adding another route parameter
I know you may think this is an overkill for simply having a "prefix" but the reason I suggest this approach is because if you have the need to add a "prefix", chances are you have the need to separate the views, models etc. as well.
You need to add a route to your route collections instead of using route attributes
routes.MapRoute(
"Route",
"prefix/{controller}/{action}",
new { controller = "Specializations", action = "Index" });
I've hit an issue with webapi attribute routing. I am calling the following route: assessment/assessments/assessmenttypes as an HttpPost. I'm getting the error that multiple controller types match the url. The problem is that they definitely don't match. Initially when I first got this issue I was using different route prefixes, however I read that webapi can ignore route prefixes, so I changed all the routes to make them unique, so the only HttpPosts which are in the "matching controllers" are defined as follows:
[Route("assessments"), HttpPost]
public async Task<System.Int32> PostCreate([FromBody]Assessment assessment)
and
[Route("assessments/assessmenttypes"), HttpPost]
public async Task<System.Int32> PostCreate([FromBody]AssessmentType assessmentType)
Both controllers have the same RoutePrefix of: [RoutePrefix("assessment")]
Can anyone help please, this is very frustrating.
Thanks in advance!
What you need to understand first at all is how routing works, that's the key to solve your problem. I can update my answer later if you give me more information (at this moment it looks like you didn't post all the required information)
As you know, routing is the process of matching requests to routes.
The handler that does the magic is the HttpRoutingDispatcher with some help of extra libraries, but basically the dispatcher resolves the request. The final goal of processing the route is to identify two elements:
Route
Values
You have two different ways of resolving routes, convention-based (templates) or by attributes. It's a good idea to be consistent to avoid confusions specially when the application grows.
The routing has two different parts, fixed segments (they must match exactly) and variables (denoted as you know by {})
The request verbs are matched in two different ways, with the attribute
[HttpGet] [HttpPost] [HttpPut]
Or confusingly in my opinion using the first part of the method, if you have 3 different methods not marked with attributes the WebApi assumes the first letters try to indicate the verb:
GetList --> HttpGet of List
GetDetail --> HttpGet of Detail
PostDetail --> HttpPost of Detail
From the official documentation if you try the following template:
routeTemplate: "api/{controller}/{id}"
And you make a request from Ajax like the following:
$.ajax("/api/today/dayofweek", {
The following controller will fail (nearly always)
public class TodayController : ApiController {
[HttpGet]
public string DayOfWeek() {
return DateTime.Now.ToString("dddd");
}
[HttpGet]
public int DayNumber() {
return DateTime.Now.Day;
}
Because it's nearly impossible to establish which one is the correct one (a human being can of course, but unfortunately machines need more programming to work with fuzzy logic) :)
The problem in your case it's not both start with the same fixed route, the problem is that it's not possible to identify whether you're saying the second part belongs to a variable or it's a fixed part (it's ambiguous)
Giving you an example; how could we establish the meaning of this?
/segmenta/segmentb
/segmenta/segmentb/segmentc (is segment c a fixed route or a parameter?)
From an architectural point of view the reality is (based on Api design) that I don't think /assestmenttypes is a subordinated resource of /assestments. If you want to design a RESTful ROA Api assestmenttypes are correctly indicated as a resource if the type depends on the assestment (as a child, not as an attribute).
I would suggest you to review your paths too.
I have a simple route in my project:
routes.MapRoute(
name: "api",
template: "api/{controller}/{action}");
In my controller I have two actions:
[HttpGet]
public string Get(string value)
{
return value;
}
[HttpGet]
public string Get(int id)
{
return id.ToString();
}
Now when I try to do a url like api/controller/get?id=1 it does not work because the framework cannot distinguish between two actions. As far as I remember it did work pretty well in ordinary web api because it's obvious that this url matches only one of the actions based on it's parameter. Did I do something wrong or it's not supported in the new MVC6?
Did I do something wrong or it's not supported in the new MVC6?
MVC Action Selector dosen't regard Action's parameters during select action. Therefore you can't have two actions correspond one route template. Except e.g actions have different Action Constraints (HttpPost, HttpGet).
Choose action logic in code.
In theory choosen logic between some actions based on parameters have to be into SelectBestActions method, but it do nothing
I have an existing API I'm moving over to WebAPI, so I'm not free to change the URL. Breaking existing clients is not an option for me.
Knowing that, the original API would accept either a Guid (an ID) or a string (a name) for a given action method. The old API handler would decipher the URL parameter and send the request to a controller action designed to accept the given parameter type.
As an example:
Get(Guid id)
versus
Get(string name)
With WebAPI, the parameter binding is greedy across value types, so depending on which is first in the controller source file, that action is the one invoked. For my needs, that's not working. I was hoping the binder would realize the conversion to a Guid would fail for a name and then select the more generic string-based action. No dice. The Guid simply comes across as a null value (interestingly since it's a value type, but that's what I'm getting in the debugger at a certain point in the processing).
So my question is how best to handle this? Do I need to go as far as implementing a custom IHttpActionSelector? I tried the attribute routing approach (with constraints), but that isn't working quite right (bummer as it looks cool). Is there a mechanism in WebAPI that accounts for this I don't (yet) know about? (I know I can hack it in by testing the string for Guid-ness and invoking the other controller method, but I'm hoping for a more elegant, WebAPI-based solution...)
I'd spent a lot of time trying to fit attribute-based routing in, but I've not got that to work. However, I did solve my particular issue using route constraints. If you register the more-constrained route first, WebAPI (like MVC) will apply the constraints and skip over more-constrained routes until it finds one that it can select, if any.
So, using my example, I'd set up routes like so:
_config.Routes.MapHttpRoute(name: "ById",
routeTemplate: "product/{id}",
defaults: new { controller = "ProductDetails" },
constraints: new { id = #"^\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?$" });
_config.Routes.MapHttpRoute(name: "ByName",
routeTemplate: "product/{name}",
defaults: new { controller = "ProductDetails" });
The first route accepts a constraint in the form of a regular expression for a Guid. The second accepts all other values, and the controller will deal with non-product names (returns a 404). I tested this in the self-hosted WebAPI server and it works fantastically.
I am sure attribute-based routing would be more elegant, but until I get that to work, it's routing the old way for me. At least I found a reasonable WebAPI-based solution.
I'm new to Web API...
Here's my basic route:
config.Routes.MapHttpRoute(
name: "Workitems",
routeTemplate: "api/{controller}/{workitemID}",
defaults: new { controller = "workitems", workitemID = RouteParameter.Optional }
);
Here's what I'd like to have:
public HttpResponseMessage Post( [FromBody] FolderModel theModel )
public HttpResponseMessage Post( [FromBody] DocumentModel theModel )
But, Web API doesn't find my second Post method. I've done lots of searching here and in Google but haven't found anything that works for me (well). I know I could add a 2nd unused parameter to the 2nd method - but that's too much of a hack. If this were normal C# code, the compiler would have no problem knowing which to choose b/c the methods have different signatures. But Web API is not smart enough.
I looked at custom constraints but that didn't seem appropriate. I also cannot use different {actions} as that violates RESTful constraints (no RPC, just resources) for my API. I also cannot put the 2nd Post on a different controller.
The only way I've gotten this to work is to wrap both FolderModel and DocumentModel in a parent object like this:
public class WorkitemCreateModel
{
public DocumentModel Document { get; set; }
public FolderModel Folder { get; set; }
}
public HttpResponseMessage Post( [FromBody] WorkitemCreateModel theModel )
Then have a single Post method that takes WorkitemCreateModel. But then it's the responsibility of the developer using my API that they must pass in WorkitemCreateModel but they must only pass in a DocumentModel object OR a FolderModel object. It's annoying too b/c my GET API can return either a DocumentModel object or a FolderModel object. So, it would be nice to just pass the object you get from the GET into the POST. But that doesn't work and they must wrap it in a WorkitemCreateModel object first.
Any other suggestions?
BTW: this website is the best! I've found SO many answers here!
This might be an old post but I am adding this info just in case for people like me who came here looking for answers.
The short answer here is NO, its not possible.
The problem is with the way the routing works, especially the part about choosing what action method to use. Here is an extract from ASP .NET article (https://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection),
The goal of the selection algorithm is to select an action from the static description, before invoking any bindings. Therefore, complex types are excluded from the matching algorithm.
So while matching action methods to the path, Web API disregards all Complex types in the parameter list for that method and when you do that both of your methods have 0 parameters and that's why you are facing this problem.
Hope this helps...
Have you considered changing the method names and trying using the [HttpPost] attribute?
Source : http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
This might answer your question:
Multiple HttpPost method in Web API controller