Current situation
[HttpGet]
public HttpResponseMessage CheckProfileStatus(int id)
{
//check profile status from third party
return //profile status and HttpStatus;
}
[HttpGet]
public HttpResponseMessage ProcessTheProfile(int profileId)
{
//check profile status from third party again, if status is "Good", do something
return //someMessage and HttpStatus;
}
Url to call CheckProfileStatus method
myUrl/api/Profile/123
Url to call ProcessTheProfile method
myUrl/api/Profile/?profileId=123
I hope the comments in the code makes the situation clear.
I don't really like the fact that I have two HttpGet methods with same signature in one controller (even though it works).
I don't know if it is best practice.
Question:
Should I extract these two methods in two seperate controllers, is it completely fine to have two HttpGet methods with same signature in one controller or is there better way to handle this situation?
First, this is confusing to any client of the API.
You have 2 GET methods which are virtually identical. In fact most people prefer to add a route covering your first option which basically sets the profileId to the value after the forward slash.
Second, the expectation is that when you issue a GET against an entity with an ID, you get the representation of that entity. This is not what's happening here.
You need to decide which kind of API do you want. A generic one where stuff like this is fine :
myUrl/api/profile/process/123 or process/profile, or whatever else makes sense to the API.
If your API is supposed to be RESTful ( which basically means you will have one method per HTTP verb and nothing more ) then you need to add a new controller for ProcessProfile and then your url can look like this :
myUrl/api/ProcessProfile/123
None of these options is set in stone, they are just that, options.
The comments to the OP thread give some good options as well, which should be considered.
ASP.NET maps HTTP requests to HTTP handlers based on a file name extension. I was just wondering how does it work in MVC, because in MVC we just point to the action method inside the controller. There is no extention we specify.
Then I tried to understand how handler works in MVC Application execution process from this link
But still I did not understand. Can anybody explain how handlers work in MVC and how it is different from normal ASP.NET Handlers.
In fact, a comprehensive explanation here would be really out of scope, you should read about it to learn it in depth, and if you accept an advise, if you are able to find one, a book is generally a better resource than blogposts and the like (in my experience, as a blog post could be written by anyone who might not have a clue). Just make sure you read the reviews to find out whether a book is good or not so you won't get mislead.
Anyway, I'll try to outline the basics, without adding too much noise with the details because that would lead too far.
As others have already mentioned in their answers, in the MVC ecosystem, there is a concept called routes and routing table. Basically, what they are, is a collection of patterns. When a request arrives, the request header contains the requested url, such as these:
http://localhost:12345/
http://localhost:12345/home/index
http://localhost:12345/products/catalog
http://localhost:12345/products/details/1
http://localhost:12345/promotion
There is a reason I've written more than one, and with different formats. Let's examine them one by one.
The http://localhost:12345/ example
The first, http://localhost:12345/ is the simplest one possible, it is usually referred to as default route. Usually this points to a parameterless Index action method in the HomeController, in the default area (that is, in "no area"). Note that both the action and the controller, as well as the area (and, well, pretty much any detail) is configurable. You make this work by adding the default route, but this is usually done for you by the project template. You can find it in the App_Start/RouteConfig file, it has a name parameter of "Default":
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
So, whenever a request comes in which no (let me say this in a primitive way) additional parts are present, they get handled by the Index action inside the HomeController. As you can see in the code above, Index and HomeController are configured in the defaults: parameter, it is there where you have to go to in order to change the defaults. If you instead use an action called Welcome, then you would need to pass this to the method above:
defaults: new { controller = "Home", action = "Welcome", id = UrlParameter.Optional }
Also note that despite I've written "in which no additional parts are present", by additional parts I only mean route segments -- for example, a query string ?param1=value1 will not prevent this route from being selected.
The http://localhost:12345/home/index example
This is the exact same as above. I've only included this so that you can see that even though this particular route is the default, nothing prevents you from actually specifying the controller and the action.
The http://localhost:12345/products/catalog example
The basic workings of this one is the same as the above two, except that this is not a default route, so you need to explicitly specify the controller and the action in order for your request to be routed to the appropriate controller and action. Look again at the configuration code above. You can see that there is a url pattern of "{controller}/{action}/{id}" defined. You do not need to create a new route because this pattern can be used for this example. Compare them and you can see why:
http://localhost:12345/products /catalog
{controller}/{action}/{id}
As you can see from the displacements, products is recognized as the controller name, catalog is recognized as the action name, and since id is given a default value of UrlParameter.Optional, the value of it may be omitted and the route will still be valid for the request. So as long as there exists a ProductsController in the default area and within it, a Catalog action which either has an id parameter, or has no parameters at all, or any existent parameter has a default value (or is nullable, in which case it will be passed null), the routing system will use this route for the request.
The http://localhost:12345/products/details/1 example
Same as above, but here, 1 is mapped to the id segment. As long as there is a ProductsController in the default area, and within it, there exists a Details action, which either has no parameters at all (since id is optinal, it may be ignored), or has a parameter called id and if every other parameters that might be present have a default value (or can be defaulted to null - which means it is a reference type), then this route can be used for the request.
The http://localhost:12345/promotion example.
This is a special one I've included for one particular purpose. Let's assume you do not have a PromotionController. You can still define a route pattern as so:
routes.MapRoute(
name: "Promo",
url: "promotion",
defaults: new { controller = "Products", action = "Promotion" }
);
As you can see, here we assume you have a ProductsController and within it, a Promotion action. Whereas you could simply request the url http://localhost:12345/products/promotion (and it will actually work) specifying a "dedicated" route can also be done. I've added this example for the following reasons:
So that you can see such a thing is possible
Because there might be cases when you need it, such as:
The path at which a particular functionality could earlier be found, is now changed, but because your users might have bookmarked that page with the old url, you don't want them to see 404s. In this case, this route is used to "reroute" (not an HTTP Redirect, just couldn't find a better word) the old url to the new one.
In some cases, you do not want to expose the controller/action hierarchy. In the case of this example, simply typing "promotion" might be a little bit more telling to the user than typing "products/promotion". You can argue with this in this example, but still, there might be better examples in which case this is feasible. Also, you can use this to provide shorter urls.
And finally, let's remark a few things about the routing system as a whole.
There might be cases when an url could be mapped to more than one routes. In this case, the order in which the patterns are defined is the determining factor. The first one that matches the url gets picked which means that you should specify your route patterns starting with the most specific (aka with fewer or no varying parts at all). An example for a pattern with no varying parts is the last url I've written, it has a hardcoded pattern of promotion. You can recognize these from having no {}s in the pattern. The least specific (in other words, more general) ones should be at the bottom of the method. If there still are overlaps, you should rethink how you define your routes.
There are other things that can affect whether or not an action can be selected to serve a request. For example, the presence of [HttpPostAttribute] on an action method will tell the action invoker (and not the routing engine) that this method can only be used if the request is a POST request (unless an [HttpGetAttribute] is also applied). Note that the routing system and the so-called action invoker are two different things - you can find yourself in a situation where the routing system will find a pattern match for a route but the action invoker finds no suitable actions - for example, as above, a pattern is matched for which there is no GET handler but the request is actually an HTTP GET. This is beyond the scope of your question, but wanted to include this as a hint so that you are aware that you need to look it up. The HttpPost and HttpGet attributes mentioned above are special ones, in order to achieve such a functionality by implementing your own attributes, your attributes must derive from the ActionMethodSelectorAttribute class.
As you said in your question, requests are not mapped to files but to controllers and actions. So suppose a matching route pattern is found, and the controller to use happens to be Home(Controller) and the action to be Index. What the system does (this is not the responsibility of the routing system from here on) it scans the assembly of the running application (using reflection) and enumerates all the classes inside of it. It looks for classes that fulfill certain criteria (I'm not sure about whether or not the following is 100% accurate and/or comprehensive):
Derives directly or indirectly from Controller
Is not abstract
Is public (not sure about this one)
Has a public constructor (not sure about this either)
The name is either Home or HomeController (not sure if simply Home works, actually it is a convention to suffix the names with the term "Controller")
If none is found, then the request cannot be fulfilled as per the matching route. If more than one are found, then the one to use cannot be determined and an error is raised.
If exactly one matching class (Controller) is found, then the system will further investigate it. It will now look for methods that:
Are action methods, that is, their return type is ActionResult or a derived class (this is not required for AJAX-requests)
Are public
Its parameters satisfy the constraints defined by the route as discussed earlier
Any ActionMethodSelectorAttribute-derived attributes applied to a particular method do not prevent the action from being executed as per the request properties.
If no or more than one matching action methods are found, the request cannot be fulfilled and an error is raised.
If there is exactly one match, it gets invoked to fulfill the request.
Probably you have confused with the terminologies.
MVC uses routing tables to find the route to particular View.
Just like you have www.YourSite.com/index.aspx, You have www.YourSite.com/Home/Index.
The difference is that, in ASP.Net, you will be directly loading the .aspx file as in the URL and in MVC, you will go to the Home controller and look for an Index action. The Index action will then render it's view.
Here, each view or partial view will have their ActionMethod in a controller.
No view is rendered without a controller and action associated!
You could have a look at this link for further reference.
you will have a default route in the Global.asax file which will do the job.
If you have any complicated ones(with multiple parameters), You need to add them to the routing table manually. more about this is in the link.
Hope this hellps.
Don't confuse it too much
all controllers are class
all actions are methods
you are just calling the methods(functions) from address bar thats all...
the mvc handler check wither there is a method(ActionRsult) in class(Controller)
with the same name and matching params thats all
I'm writing an API using WebAPI, and whilst I'm designing this from scratch, I'm trying to make it RESTful. In the past when doing web service work, I've always used RPC patterns (ASMX, SOAP, etc) - eg. using an arbitrary method name that matches what I want it to do. To me this seems much more intuitive and explicit than REST, but given WebApi seems to be more RESTful by nature (I know you can change the route so it's not), I've decided to try and make it RESTful.
I understand (I think!) the basics - eg. POST for create, PUT for update when specifying an ID, GET to get, DELETE to delete, etc, etc.
My confusion is how to deal with returning collections vs a single object. For example, say that I've got an API controller called UsersController. As far as I gather, I'd have the following URLs:
GET: /api/users (lists all users)
GET: /api/users/1 (lists details about user with ID 1)
POST: /api/users (creates a new user with details sent in the POST data)
PUT: /api/users/1 (updates the existing user with ID 1 with details sent in the POST data)
DELETE: /api/users/1 (deletes the existing user with ID 1)
For the first URL above, I need to also send various filter/search criteria. Does this criteria just get passed through as query string parameters? Or should I be using a completely different controller for this "search" functionality? Eg. UserSearchController? If so, should that be a POST in this case or still a GET? Actually as I write this, I'm wondering whether a separate controller makes sense anyway, as I'll probably want more detail returned in the GET for a single user than in the search results. Does it make it not RESTful if the same controller does return different data for a single object GET vs a GET returning a collection?
For the first URL above, I need to also send various filter/search criteria. Does this criteria just get passed through as query string parameters?
It definitely makes sense to use query string for specifying filter/search parameters.
should I be using a completely different controller for this "search" functionality? Eg. UserSearchController?
You should not do that. There are couple of reasons that i see here:
There will be almost identical functionality in GET: /api/users
You can easily implement GET: /api/users, GET: /api/users?filter=...&sort=... and GET: /api/users/1 in one method:
//If you are using EF it could look like
//filter and sort arguments could be added here as well
public HttpResponseMessage Get(int? id)
{
if(id.HasValue)
{
return Request.CreateResponse(
HttpStatusCode.OK,
Context.Users.SingleOrDefault<Users>(u => u.Id == id));
}
var users = Context.Users.Select(apply filter).OrderBy(apply sort).ToList();
return Request.CreateResponse(HttpStatusCode.OK, users);
}
You can take a look at OData - it may help you with the implementation.
Spreading this logic between different controllers compromises single responsibility principle - your users controller should handle all the logic related to users and only this logic
If so, should that be a POST in this case or still a GET?
If you want to make your API RESTful you should be using GET. BUT you should be aware, that returning an array of JSON objects through GET could be potentially vulnerable to JSON hijacking. One of the simplest solutions to this exploit is allowing to get JSON arrays through POST only (there are other solutions as well).
I'll probably want more detail returned in the GET for a single user than in the search results. Does it make it not RESTful if the same controller does return different data for a single object GET vs a GET returning a collection?
This is totally fine to return more details for a single object than for a collection. It doesn't affect RESTfulness of your API in any way.
COMMENT
You wrote:
PUT for update when specifying an ID
Actually it is not entirely correct:
PUT should be used for complete replacement of the entire entity
PATCH should be used to perform a partial update.
If you want to get passed through criteria as query string parameters to URI, you can do that using Attribute Routing into WebApi. I think attribute-routing-in-web-api helps to you.
Yes, I would pass filter parameters as query string options. The 'restfulness' of your application is not dependent on controller structure, so you can follow a structure that best suits your application
I'm working on a website that has 4 individual sections, each with their own controller. These controllers share a few models, however the model will do things slightly differently based on the controller calling it.
The route that I'd like to follow is {controller}/{model}/{id}/{action} so I could use controller1/cars/5/edit rather than controller1/editcar/5.
Is there any way to have my controller understand a model+action combination and handle them with different ActionResults, or am I forced to use an ActionResult on the model name and then use a conditional to figure out what action to take?
In Ruby I could use get "/controller1/cars/:id/edit" do. I'm just looking for something similar in MVC4.
Example routs:
/controller1/cars
(returns a view containing all cars from controller1)
/controller1/cars/5
(returns a view containing car with the ID 5 from controller1)
/controller1/cars/5/edit
(returns a view to edit car with ID 5 from controller1)
/controller2/cars
(returns a view containing all cars from controller2)
/controller2/boats
(returns a view containing all boats from controller2)
I think this route meets your needs. It requires some clever logic in your action methods but should give you the tools to handle it. Read my description of behavior.
routes.MapRoute(
"Default", // Route name
"{controller}/{model}/{id}/{action}", // URL with parameters
new { controller = "Home", action = "View", id = UrlParameter.Optional } // Parameter defaults
);
This route will Default to an action called View (that will presumably be used for Display) and has an option to direct to a different Action.
Model and id will be passed as arguments to you action method. Unfortunately, Model will not be sent as a type but a string (you may feed that into a Factory class).
if if is left out (eg /controller2/boats) it will be passed to your action as a null. This requires logic to handle but gives you the tools to handle it.
Thanks for the responses. The reason that I was having so much trouble with this was because I couldn't figure out how to separate controllers with a rout properly. This resulted in my idea breaking away from MVC standards and almost trying to implement a controller of controllers.
When I finally stumbled upon "Areas", I quickly realized that this is what I needed. I was able to add an Area for each section (or area) of my website, and within each area, I could define individual controllers and views, while my models remained in a shared directory outside of all areas. This now works perfectly.
Suppose that I have a nested one to many-type hierarchy database as follows:
One Region has many Countries; each Country has many Cities; a City must belong to one and only one country.
Abstracting this information into a RDBMS is a trivial exercise, but (to my mind) the most sensible REST endpoint to return a list of countries for a given region id would be something like the following:
HTTP GET http://localhost/Region/3/Countries
By default, the .NET Web API's routing would be, at best, http://localhost/Countries/Region/3 or http://localhost/Region/Countries/3.
Is there a sensible naming-convention I should follow, or is the routing customisable enough to allow URIs to take any shape I like?
The routing should be customizable enough to get the URLs you're looking for. Assuming you want URLs in the form 'http://localhost/Region/3/Countries', you could register this custom route:
config.Routes.MapHttpRoute("MyRoute", "Region/{regionId}/Countries", new { controller = "Region", action = "GetCountries" });
This would dispatch requests to the 'GetCountries' action on the 'RegionController' class. You can have a regionId parameter on the action that gets model bound automatically for you from the URI.
You may want to look online for the attribute routing package for WebAPI since it may be more appropriate in your case.
Routings should be quite flexible - the question would be how you'd like to serve the data. Do you have one controller in mind or multiple?
If you had a RegionController I don't see why you couldn't configure a route:
routes.MapHttpRoute(
name: "CountryList",
routeTemplate: "{controller}/{regionId}/countries"
);
And a corresponding method:
public CountryCollection Get(int regionId)
Or am I missing something in your question? Where does your default routing come from?
Have a look at their documentation:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api