MVC request handler - c#

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

Related

How to Deal with Unused MVC Routing Parameters

I have an MVC application and the UI team has requested that urls for a careers page follow this format: mydomain.com/careers/{jobTitle}/{JobCode}. I can do that in the careers controller like this:
public ActionResult Detail(string jobTitle, string jobCode)
{
var model = getModelFromDb(JobCode);
return View(model);
}
The problem is that we look up the career info by job code not title so we end up with an unused parameter. I don't think this is best practice. Especially since you can enter whatever you want for the career title and still get the job details back. A url like this would still work: mydomain.com/careers/yourmom/1234.
I suppose we could always look up careers by title and code but that seems kind of pointless since the codes are unique.
Is there a better way to implement the extra parameter in the url and keep from allowing invalid job titles from being put in the url?
You can create your own custom route constraint, this will allow to match only routes that contain jobtitle that match the jobcode.
You will have to inherit from the IRouteConstraint interface and implement the Match method. One of the parameters that the method receive is a RouteValueDictionary.
It is a collection of key/value pairs containing the routing parameters. You can use them to look up the job title and make sure it is coherent with the job code.
See this example for more info.
In this way you will be able to receive only correct routes and reject incorrect routes like mydomain.com/careers/yourmom/1234
What we ended up doing is passing both parameters to the controller(jobTitle and jobCode), looking up the record by jobCode, and validating that the record's slug matches the jobTitle passed in(all case insensitive). This way we prevent bogus urls from returning job detail pages and we keep the UI team happy.

Using [Bind(Prefix="xyz")] at controller level?

I have a standard set of routes, used across controllers, such as {controller}/{action}/{clientId}/{id}. All controllers are using each of these values, but each controller may use {id} in a different context.
For example, an {id} on a LabController may be "labId", and {id} on MembershipController may be "membershipId", etc. Instead of using "id" in every action method on each controller, I'd like to pass in "labId" and "membershipId" as parameters for actions in their respective controllers.
I could use [Bind(Prefix="id")] for every single action, but I was hoping there could be a way to control it at the controller level. I'm also trying to avoid multiple (nearly identical) routes for similar paths. Thanks.
EDIT: to clarify, I'm trying to bind these to parameters on my actions. Such as:
public ActionResult GetLab(int labId)
or
public ActionResult GetMembership(int membershipId)
All using the same route - just binding the {id} part as an alias for, in these cases, labId and membershipId, without having to use [Bind] every time.
You shouldn't need to bind a parameter name to the route at all. Keep in mind that the generic "id" parameter name is just a placeholder for the passed in value. So your routes become:
Lab/SomeAction/1
and
Membership/SomeAction/1
It doesn't care what the paramter name is in this case. You are thinking more in terms of query string parameters and not route parameters.

Redirect Controller to Another Controller

Say for example, I have ControllerA which has about 10 Actions in it, when a user types into the URL /ControllerA/{Any Action name} it should redirect to /ControllerB/Index
Is there a simple way to redirect all actions in a ControllerA to ControllerB without having to write RedirectToAction in all the actions in ControllerA
The question is not very clear -
I can see potentially three different scenarios (maybe more?)
1. Do you actually need to redirect from all these actions before doing anything (so why are they even there?),
2. Do you want them to execute and then go to ControllerB.
3. Do you just want to invoke ControllerB directly for all these 10 Urls?
For scenario one you can just use attribute routing on controllerA (or even on controllerB) and have a single method that redirects to index.
For scenario two you want to override OnActionExecuted
For scenario three you want to put attribute routes directly on Index with all the paths that lead there.
Edit:
you can do something in routing that will short circuit controller A.
if (configurationCondition)
{
routes.MapRoute("Hijaack",
url: "controllerA/{*theRest}",
defaults: new { controller = "controllerB", action = "Index" });
}
This is more loose than requiring the actions to match exactly to what is on controllerA. It avoids the redirect completely, which should be a nicer experience to the end user.
It can be tightened up to more specific routes with attribute routing.
There are a few other way of doing this without affecting the ControllerA which would be more suitable:
by using a filter in which when you see that the user is asking for this controller you return a redirect response to the 2nd controller
by using a routing rule
Both can be implemented by taking care of your web config settings.

Handling extra MVC parameter with different ActionResult

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.

Custom Routing with ASP.NET Web API

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

Categories

Resources