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 want to use method Html.Action() in my project.
I have two projects.
project 1 - area - HomeController - IndexAction
project 2 - i write a function helper to use in my layout.
public static IHtmlString RenderTest(this HtmlHelper htmlHelper)
{
string mhs = "";
mhs += htmlHelper.Action("Index", "Home", new { area = "area" });
return new MvcHtmlString(mhs);
}
for project 1, I write a route map:
context.MapRoute("area_default",
"Theme/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { string.Format("{0}.Controllers", this.GetType().Namespace) }
);
How can I use this function to load a controller that is in another assembly?
Html.Action("Index","Home", new { area = "area" });
In addition I have a duplicate controller names in each assembly,
e.g. Namespace1.FooController and Namespace2.FooController
I don't have a problem with my routes. Also, I can call any controller in different assemblies via URL/routes.
But I cann't use these urls in my HtmlHelper.Action().
Actually I want to call an action in controller that is in another assembly and get the view of that action as HtmlString but processed.
If you are sure you don't have duplicate controller names in each assembly, e.g. Namespace1.FooController and Namespace2.FooController, then you can simply just add all the namespaces that should be searched in the array for the namespaces param.
However, if you do have duplicate names, then the route will end up matching multiple controllers, which is a problem. There's no order of ops to the namespaces parameter - all are searched and all are treated equally, despite the order of the namespaces. If that's the case, then you'll have to define multiple routes, each tied to a specific namespace.
UPDATE
actually i want call a action in controller in another assembly and get the view of that action as HtmlString but processed.
Oh. You mean you literally want to call the action like a method in another piece of code, rather than get to it via a URL? You could use something like WebClient to actually make a request and get the response as a string. That's admittedly probably not the most efficient way, but it likely would be the easiest.
The alternative is much more complicated. If it's just a regular action that returns a ViewResult, then that's what you'll actually get back from calling it like a method. However, from a quick debugging session, it appears that the ViewResult is not processed until after it's returned from the action and goes back into pipeline (not just another action). As a result, all you get is an object with the name of the view to be used and all the view data that should be used to render it with, i.e. not an actual rendered string that you could do anything with. However, using something like RazorEngine, you might be able to manually take the data from the ViewResult and do something with it.
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
So I have a controller called EmployeeController, and all the Views are in /Employee.
I'd like to create a route so that the EmployeeController will use /Employees and /Employees/Add instead of /Employee and /Employee/Add.
I keep finding articles about how to change the route to go to different actions, but I couldn't find any way to do this.
I think you're confusing Views with Routes. ASP.NET MVC relies a lot on convention, and in this example it takes the controller component of the route an applies it to find the controller. You can define a new route:
routes.MapRoute("Employees", "employees/{action}", new {
controller = "Employee",
action = "Index" });
Actually, there are 2 different questions:
First one is about routes mapping and here I'd agree on simple solution suggested by Matthew Abbott and Bugai13.
Second one is about "Views" folder conventions and View files resolution. If you want some custom logic about that you may inherit ViewResult and change the way it finds the appropriate View file. You can also dive deeper into framework and tweak the way View is found and instantiated by creating your own IViewEngine or customizing one of those already existing.
It seems though, that all you need is first thing - just provide more specific route mappings with URL pattern like employees/{action} and you're done.
Why not just rename EmployeeController to EmployeesController? Then you don't have to mess with the route.
Of course you will then have to change your Views\Employee folder to Views\Employees as well.
UPDATE 2
Ok - So it looks like my question is changing again slightly :-)
I've realised now that UrlHelper.Action doesn't seem to correctly resolve the URL in any Area unless the area name is explicitly specified. If it's not specified it seems to return whatever area name we're currently in which makes it look like it's working from one part of the site but then the same link in another area resolves to the wrong Area name.
Either I've done something funky to make it do this or I'm not quite understanding how this Action method is meant to work.
UPDATE 1
I can make this work by doing the following:
return helper.Action("add", "product",new {area = "storemanagement"});
which changes my question slightly.
How come the MVC routing doesn't disambiguate the controllers with the same name and resolve to the one with the action method specified?
Origional post
Hey everyone,
I've created a helper method on the UrlHelper class and am having a small problem with one of the routes.
Here's the code for the helper method in question:
public static string AddProduct(this UrlHelper helper)
{
return helper.Action("add", "product");
}
I basically have two controllers named "product" which are in different areas of the site. One of them in used for browsing of products and the other for management of products. Only one of the product controllers contains an action method "Add".
When I output the value of AddProduct
<%: Url.AddProduct() %>
The area name is resolved to the current area I'm browsing and not the correct area for the product controller containing the Add action method.
Is there something I need to set up in the routes? I'm not exactly sure how the routing works with UrlHelper.Action so I dunno if it's possible to do what I'm trying.
Cheers for any help.
Just to put the answer in the answer section for clarity...
You'll need to add Area to the RouteValues anytime you use UrlHelper.Action to generate a link path.
If you're in the Controller, you can stub a UrlHelper like this:
var httpContext = new HttpContextWrapper(System.Web.HttpContext.Current);
var requestContext = new RequestContext(httpContext, new RouteData());
var urlHelper = new UrlHelper(requestContext);
You can always get the current area like this:
HttpContext.Current.Request.RequestContext.RouteData.DataTokens["area"]
Finally, when using UrlHelper pass the area in the RouteValues object like this:
var url = urlHelper.Action("Index", "Home", new { area = "Pages" }));
That is the default behavior of ASP.NET Routing.
When in an "Area" the action (and view) are searched for by controller and action name in default locations.
The view is presumed to be ActionName unless stated otherwise in the action e.g. return PartialView("_MyPartialView.cshtml", ViewModel)
The default locations are these: {Controller} = controller name, {Area} = area name
Controller:
"Areas/{Area}/{Controller}/"
"Controllers/{Controller}"
Views:
"Areas/{Area}/Views/{Controller}/"
"Areas/{Area}/Views/Shared/"
"Views/Shared"
If you don't give the Area in the route values it will never search outside these locations. The Area is the same as the location where you are calling your helper. If you are in the root level (not in an area) it will be limited to
"Controllers/{Controller}" // controller
"Views/{Controller}/" // views
"Views/Shared" // views
The problem was that when you where in one Area1 it searched for
"Areas/Area1/{Controller}/" and when you were in Area2 it searched "Areas/Area2/{Controller}/" (And both searched the Controllers/Product and Controllers/Shared). It was able to find it when you where in the right area because it fit the default search location, but not while in the other area because the controller was only in the one physical area.
Adding the Area as you did, tells it to search in a predefined Area so it will go directly there.
"Areas/Storemanagement/Views/product/" and search for the view defined in the Add Action.
There is little point in having an empty helper method returning a Url.Action method, but perhaps that was just for demonstration.
(I just noticed the question is quite old but I guess this can be here for future reference)