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 upgrading an ASP.NET MVC 4 project to MVC 5 and want to use attribute routing instead of convention routing. So far, so good, but I have one issue with populating the Defaults RouteValueDictionary. How can this be accomplished with attribute routing?
I am using multiple routes for the same action, each passing a different enum value to determine which type the Action is. The value of the enum will not be visible in the route directly though! This is important, otherwise I could use the value of the enum parameter in the route template.
My simplified Controller Action:
public class MyController : Controller
{
public ActionResult MyAction(MyType myTypeValue)
{
// ...
}
}
public enum MyType
{
FirstOption,
SecondOption
}
My old convention routes:
routes.Add("First", new Route("a-route", new { controller = "MyController", action = "MyAction", myTypeValue = MyType.FirstOption }));
routes.Add("Second", new Route("a-total/different-route", new { controller = "MyController", action = "MyAction", myTypeValue = MyType.Second }));
With attribute routing i was expecting to use something like this:
Route["a-route", new { myTypeValue = MyType.FirstOption }]
Route["a-total/different-route", new { myTypeValue = MyType.SecondOption }]
But unfortunately, this does not exists. I've tried to make a custom RouteAttribute that accepts an object to populate the Defaults RouteValueDictionary:
public class MyRouteAttribute : RouteFactoryAttribute
{
private RouteValueDictionary _defaults;
public Route(string template, object defaults)
:base(template)
{
_defaults = new RouteValueDictionary(defaults);
}
public override RouteValueDictionary Defaults
{
get { return _defaults; }
}
}
But this is not working since the route attribute cannot handle anonymous types compile time.
Does anyone know a way to get this working one way or another?
"Just make two different actions" is not an option here.
First of all, it is unclear why you would want to change from convention-based routing to the (less flexible) attribute-based routing, especially considering some of the features you are interested in are not supported by the latter.
But if you are insistent on changing to attribute routing just because it "looks cool", then you have a couple of options.
Option 1: Make Separate Action Methods
If you use 2 different actions and return one action from the first, you generally won't have to rewrite logic. But this is the only native support in attribute routing for setting optional parameters. An example of how you can support optional parameters with Enum can be found here.
[Route("a-route")]
public ActionResult MyAction(MyType myTypeValue = MyType.FirstOption)
{
return View("Index");
}
[Route("a-total/different-route")]
public ActionResult My2ndAction(MyType myTypeValue = MyType.SecondOption)
{
return MyAction(myTypeValue);
}
Option 2: Hack the Attribute Routing Framework
Microsoft intentionally made the attribute routing framework non-extensible by using several internal/private types to load the RouteValueCollection with the attribute routes.
You could potentially hack the attribute routing framework to provide your own logic as I have done here. This requires using Reflection, but since it runs at the start of the application rather than per-request the overall performance impact will be minimal.
But depending on your requirements, you may need to copy more of the logic from the MVC attribute routing framework to populate your routes, which may not be worth the effort. In my simple case of supporting multiple cultures it was. In your case you will need to support your own attribute types with additional parameters, which will be more challenging.
But if you need more flexibility than this, I would suggest sticking with the convention-based routing.
Attributes have limitations on which datatypes are supported as opposed to code-based solutions.
Several features including populating default route values, using constraints, and building custom routes are either much more difficult or not supported when using attribute routing.
The bottom line is, attribute routing is not the holy grail of routing. It is another routing option added in MVC 5 which can be used under a limited subset of routing scenarios of which convention-based routing is capable of. It is not and should not be viewed as a routing "upgrade" just because it happens to not have been an option until MVC 5.
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.
In WebAPI you can specify an Order in RouteAttribute to determine which order the routes are matched in. For example the below will match /other to GetOther before matching /blah to GetByName
[HttpGet, Route("{name}", Order = 1)]
public string GetByName(string name) { ... }
[HttpGet, Route("other")]
public string GetOther() { ... }
How would I do the same but with RoutePrefix (which doesn't have an Order property)? If it did it would looks something like this:
[RoutePrefix("foo", Order = 1)]
public class FooController : ApiController { ... }
[RoutePrefix("foo/bar")]
public class FooBarController : ApiController { ... }
Doing the above (without the imaginary Order property) throws the following message when calling /foo/bar:
Multiple controller types were found that match the URL
Is there existing functionality for getting around this (preferably with attributes)?
I don't believe Microsoft's attribute routing has support for ordering routes by controller.
When you specify an Order property on an action's RouteAttribute, you are specifying the order within the controller only.
AFAIK, the attribute routing algorithm will scan all of the controllers alphabetically. Then within each controller, is will use the Order property of any RouteAttributes to decide the order of action routes within that controller.
This means if you have route collisions spread across different controllers, you should either rethink the design or make sure the controllers with the more specific route patterns are named alphabetically before the controllers with the more general route patterns. Otherwise, you may run into that "ambiguous route / multiple actions with matching routes found" exception.
Update: The answer above is for Microsoft's AttributeRouting implementation, which was based on another very popular open source project that came before MVC5. In that library, you could order attribute routes by controller, though I think the property was SiteOrder or something like that.
You can add a orderby to the loop in index.cshtml:
#foreach (var group in apiGroups.OrderBy(g => g.Key.ControllerName))
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.